Goal
Ectoを使って多対多の関係を構築する。
フォロー機能を実装する。
Dev-Environment
OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.15
PostgreSQL: postgres (PostgreSQL) 9.4.4
Wait a minute
フォロー機能を例に、Ectoによる多対多の関係を実装する。
Ectoの参考記事は以下のリンク先。
yoavlt氏、素晴らしい記事をありがとうございます!参考にさせて頂きます!!
参考: Qiita - Elixir Phoenixのデータベース操作モジュールEcto入門 多対多
Index
Many to many
|> Preparation
|> Get an intermediate table
Preparation
まず準備。
新しく検証用のプロジェクトを作成。
>cd プロジェクト作成ディレクトリ
>mix phoenix.new followed_users
... (選択肢はyes)
>cd followed_users
>mix ecto.create
>mix phoenix.server
>ctrl+c
以降、プロジェクトと言えば、
followed_usersを指し示す。
データモデルは以下。
-
ユーザのデータモデル
- モデル: User
- テーブル: users
- 生成カラム名(カラム名:型): name:string, email:string
- 自動生成カラム名(カラム名:型): id:integer, inserted_at:timestamp, updated_at:timestamp
-
中間テーブルのデータモデル
- モデル: Relationship
- テーブル: relationships
- 生成カラム(カラム名:型): follower_id:integer, followed_id:integer
- 自動カラム(カラム名:型): id:integer, inserted_at:timestamp, updated_at:timestamp
- インデックス: follower_id, followed_id, follower_idとfollowed_idでの複合インデックス(ユニーク)
Description:
インデックスを詳しく知りたい方は、hexdocs - v0.14.3 Ecto.Migration.index/3を参照して下さい。
Caution:
インデックスが必要ない方はやらなくて問題ないです。
自動生成でソースコードを生成
- User
>mix phoenix.gen.html User users name:string email:string
- Relationship
>mix phoenix.gen.html Relationship relationships follower_id:integer followed_id:integer
ファイル: web/router.ex
示されたルーティングを追加する。
scope "/", FollowedUsers do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/users", UserController
resources "/relationships", RelationshipController
end
ファイル: priv/repo/timestamp_create_relationship.exs
インデックスを追加。
defmodule FollowedUsers.Repo.Migrations.CreateRelationship do
use Ecto.Migration
@disable_ddl_transaction true
def change do
create table(:relationships) do
add :follower_id, :integer
add :followed_id, :integer
timestamps
end
create index(:relationships, [:follower_id], concurrently: true)
create index(:relationships, [:followed_id], concurrently: true)
create index(:relationships, [:follower_id, :followed_id], unique: true, concurrently: true)
end
end
マイグレーションの実行。
>mix ecto.migrate
...
これで準備完了。
Get an intermediate table
中間テーブルとの関係を構築します。
ファイル: web/models/user.ex
Userのschemaを以下のように編集する。
schema "users" do
field :name, :string
field :email, :string
# User who follow
has_many :followed_users, FollowedUsers.Relationship, foreign_key: :follower_id
has_many :relationships, through: [:followed_users, :followed_user]
# Followers the user
has_many :followers, FollowedUsers.Relationship, foreign_key: :followed_id
has_many :reverse_relationships, through: [:followers, :follower]
timestamps
end
Description:
逆転していて分かり辛くなっていると思うので、説明を書いておきます。
followed_users: フォローしているユーザ
follower_idをキーにして、フォローしているユーザを取得している。
has_many :followed_users, FollowedUsers.Relationship, foreign_key: :follower_id
has_many :relationships, through: [:followed_users, :followed_user]
followers: フォロワーのユーザ
followed_idをキーにして、フォローされているユーザを取得している。
has_many :followers, FollowedUsers.Relationship, foreign_key: :followed_id
has_many :reverse_relationships, through: [:followers, :follower]
ファイル: web/models/relationship.ex
Relationshipのschemaを以下のように編集する。
schema "relationships" do
belongs_to :followed_user, FollowedUsers.User, foreign_key: :follower_id
belongs_to :follower, FollowedUsers.User, foreign_key: :followed_id
timestamps
end
テスト用データを適当に画面から用意して、
iex上でデータ取得ができるか試してみましょう。
(二件のユーザと一件のリレーションシップが最低)
iex起動。
何回も書くのが面倒くさいので、aliasを定義。
>iex -S mix
iex(1)> alias FollowedUsers.User
nil
iex(2)> alias FollowedUsers.Relationship
nil
iex(3)> alias FollowedUsers.Repo
nil
iex(4)> import Ecto.Query
nil
データを取得してみる。
iex(5)> [user] = Repo.all(from(u in User, where: u.id == 1, preload: :relationships, preload: :reverse_relationships))
Description:
以下のやり方でも取得できる。
user = Repo.get(User, 1) |> Repo.preload(:relationships) |> Repo.preload(:reverse_relationships)
フォローしているユーザのidを取得。
iex(7)> for followed_user <- user.followed_users do
...(7)> IO.puts followed_user.followed_id
...(7)> end
フォローされているユーザのidを取得。
iex(6)> for follower <- user.followers do
...(6)> IO.puts follower.follower_id
...(6)> end
Speaking to oneself
多対多の関係性に対して認識が間違えてないか不安になってきた。
Bibliography
Ruby on Rails Tutorial - 第十一章
hexdocs - v0.14.3 Ecto.Schema.has_many/3 :through
Qiita - Elixir Phoenixのデータベース操作モジュールEcto入門 多対多
PartyIX - has_many throughで,class_nameとかforeign_keyをちゃんと復習してみる
hexdocs - v0.14.3 Ecto.Migration.index/3