LoginSignup
13
18

More than 5 years have passed since last update.

[Elixir+Phoenix]Ectoで多対多の関係を作成する

Last updated at Posted at 2015-09-02

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

13
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
18