Elixir Ectoを使用したPostgreSQLの3番目のテーブルなしの多対多の関係

画像



コミュニケーションのために3番目のテーブルを使用することは、多くの人にとって不要な場合があり、プロジェクトの開発にさらに困難を加えます。 PostgreSQL 9.1で追加された配列列を使用して3番目のテーブルを使用しないようにしましょう



Demoと呼ばれる小さなElixir Phoenixアプリケーションを作成して、デモを行いましょう。



$ mix phoenix.new demo $ cd demo
      
      





作成したテストを使用して順番に確認してください。



 $ mix test
      
      





次に、グループに属するグループおよび投稿モデルを作成します。



 $ mix phoenix.gen.model Group groups name:string $ mix phoenix.gen.model Post posts name:string body:text group_id:references:groups
      
      





次に、複数のグループに属することができるユーザーモデル(ユーザー)を作成します。 また、ユーザーは自分のグループの投稿エントリにのみアクセスできます。 ユーザーとグループをリンクする3番目のテーブルを作成する代わりに、usersテーブルにgroup_ids列を追加しましょう。



 $ mix phoenix.gen.model User users name:string group_ids:array:integer
      
      





Userモデルは次のようになります。



 # web/models/user.ex defmodule Demo.User do use Demo.Web, :model schema "users" do field :name, :string field :group_ids, {:array, :integer} timestamps() end @doc """ Builds a changeset based on the `struct` and `params`. """ def changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :group_ids]) |> validate_required([:name, :group_ids]) end end
      
      





変更セットメソッドを使用すると、group_idsを変更できることに注意してください。 そのため、このメソッドを使用してユーザー自身でユーザープロファイルを編集すると、ユーザーは自分を任意のグループに追加できるようになります。 このロジックが自分に合わない場合は、追加の検証を追加して、group_idsの値がユーザーに許可されているグループのサブセットであることを確認できます。 または、ユーザーがgroup_idを変更できないようにすることもできます。




group_idsにインデックスを追加することもできます



 CREATE INDEX users_group_ids_rdtree_index ON users USING GIST (group_ids gist__int_ops);
      
      





このために追加の移行を作成できます。



次に、 Post.accessible_by / 2メソッドを計画します。このメソッドは、ユーザーが使用できるグループからすべてのPostエントリを返します。 これを行うには、テストを作成します。



 # test/models/post_test.exs defmodule Demo.PostTest do use Demo.ModelCase alias Demo.{Post, Group, User} #    changeset test "accessible for user" do g1 = %Group{} |> Repo.insert! g2 = %Group{} |> Repo.insert! g3 = %Group{} |> Repo.insert! %Post{group_id: g1.id} |> Repo.insert! p21 = %Post{group_id: g2.id} |> Repo.insert! p22 = %Post{group_id: g2.id} |> Repo.insert! p31 = %Post{group_id: g3.id} |> Repo.insert! user = %User{group_ids: [g2.id, g3.id]} |> Repo.insert! post_ids = Post |> Post.accessible_by(user) |> Ecto.Query.order_by(:id) |> Repo.all |> Enum.map(&(&1.id)) assert post_ids == [p21.id, p22.id, p31.id] end end
      
      





メソッドの実装:



 # web/models/post.ex defmodule Demo.Post do use Demo.Web, :model schema "posts" do field :name, :string field :body, :string belongs_to :group, Demo.Group timestamps() end @doc """ Builds a changeset based on the `struct` and `params`. """ def changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :body]) |> validate_required([:name, :body]) end def accessible_by(query, user) do from p in query, where: p.group_id in ^user.group_ids end end
      
      





ここでは、すべてのユーザーグループからすべての投稿エントリを取得します。



さらに進んで、Postエントリが一度に複数のグループに属するようにすることができます。 これを行うには、 usersテーブルと同じ方法でgroup_ids列をpostsテーブルに追加し、 group_id列を削除します 。 これで、Postレコードとユーザーgroup_ids配列にgroup_ids配列に少なくとも1つの共通要素がある場合にのみ、Postレコードをユーザーが使用できるようになります。



これを行うには、PostgreSQLでオーバーラップ演算子を使用できます 。 変更された投稿モデル:



 # web/models/post.ex defmodule Demo.Post do use Demo.Web, :model schema "posts" do field :name, :string field :body, :string field :group_ids, {:array, :integer} timestamps() end @doc """ Builds a changeset based on the `struct` and `params`. """ def changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :body, :group_ids]) |> validate_required([:name, :body, :group_ids]) end def accessible_by(query, user) do from p in query, where: fragment("? && ?", p.group_ids, ^user.group_ids) end end
      
      





演習として、移行をアップグレードして投稿テーブルと投稿モデルのテストを作成することもできます。 投稿テーブルのgroup_ids列に必ずインデックスを追加してください。



これが少なくとも誰かに役立つことを願っています。 ありがとう



All Articles