Goal
“Ecto.Schema.has_many/3 :through” の動作を検証する。
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
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.15
PostgreSQL: postgres (PostgreSQL) 9.4.4
Caution:
さりげなく、Phoenixのバージョン上がっているので注意!!
archiveの方をバージョンアップしてたの忘れていました。
Tutorialの方はv0.13.1なので・・・
さりげなく、Phoenixのバージョン上がっているので注意!!
archiveの方をバージョンアップしてたの忘れていました。
Tutorialの方はv0.13.1なので・・・
Wait a minute
フォロー機能の実装を行うために、
Ecto.Schema.has_many/3 :through の動作を検証します。
Ecto.Schema.has_many/3 :through の動作を検証します。
検証は段階に分けて全2記事で行う。
Ectoの参考記事は以下のリンク先。
yoavltさん、素晴らしい記事をありがとうございます!参考にさせて頂きます!!
参考: Qiita - Elixir Phoenixのデータベース操作モジュールEcto入門 多対多
yoavltさん、素晴らしい記事をありがとうございます!参考にさせて頂きます!!
参考: Qiita - Elixir Phoenixのデータベース操作モジュールEcto入門 多対多
フォロー機能の参考記事は以下のリンク先。
h3potetoさん、素晴らしい記事をありがとうございます!参考にさせて頂きます!!
参考: PartyIX - has_many throughで,class_nameとかforeign_keyをちゃんと復習してみる
h3potetoさん、素晴らしい記事をありがとうございます!参考にさせて頂きます!!
参考: PartyIX - has_many throughで,class_nameとかforeign_keyをちゃんと復習してみる
Index
Many to many
|> Preparation
|> Get an intermediate table
|> 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を指し示す。
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を参照して下さい。
インデックスについては、hexdocs - v0.14.3 Ecto.Migration.index/3を参照して下さい。
Caution:
インデックスがいらない人はやらなくて問題ないです。
私自身が必要になるので、ついでに検証しています。
インデックスがいらない人はやらなくて問題ないです。
私自身が必要になるので、ついでに検証しています。
自動生成でソースコードを生成。
ファイル: web/router.ex
ルーティングは以下のようになる。
ルーティングは以下のようになる。
scope "/", FollowedUsers do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/users", UserController
resources "/relationships", RelationshipController
end
- User
>mix phoenix.gen.html User users name:string email:string
示されたルーティングを追加する。
- Relationship
>mix phoenix.gen.html Relationship relationships follower_id:integer followed_id:integer
示されたルーティングを追加する。
ファイル: 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
...
12:20:31.904 [info] == Running FollowedUsers.Repo.Migrations.CreateUser.change/0 forward
12:20:31.904 [info] create table users
12:20:31.932 [info] == Migrated in 0.2s
12:20:31.982 [info] == Running FollowedUsers.Repo.Migrations.CreateRelationship.change/0 forward
12:20:31.982 [info] create table relationships
12:20:31.999 [info] create index relationships_follower_id_index
12:20:32.010 [info] create index relationships_followed_id_index
12:20:32.020 [info] create index relationships_follower_id_followed_id_index
12:20:32.028 [info] == Migrated in 0.4s
これで準備完了。
Get an intermediate table
中間テーブルとの関係を構築します。
ファイル: web/models/user.ex
Userのschemaを以下のように編集する。
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をキーにして、フォローしているユーザを取得している。
follower_idをキーにして、フォローしているユーザを取得している。
has_many :followed_users, FollowedUsers.Relationship, foreign_key: :follower_id
has_many :relationships, through: [:followed_users, :followed_user]
followers: フォロワーのユーザ
followed_idをキーにして、フォローされているユーザを取得している。
followed_idをキーにして、フォローされているユーザを取得している。
has_many :followers, FollowedUsers.Relationship, foreign_key: :followed_id
has_many :reverse_relationships, through: [:followers, :follower]
ファイル: web/models/relationship.ex
Relationshipのschemaを以下のように編集する。
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上でデータ取得ができるか試してみましょう。
(二件のユーザと一件のリレーションシップが最低)
iex起動。
何回も書くのが面倒くさいので、aliasを定義。
何回も書くのが面倒くさいので、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
多対多の関係性に対して認識が間違えてないか不安になってきた。
それはさておき、これでようやっと準備が整いました。
次は、マイクロポストに紐づくように関係の構築をしていきます。
次は、マイクロポストに紐づくように関係の構築をしていきます。
続きの記事: many to many (Part 2)