【Rails】モデルの関連付けの読み方
最終的なコード
最終的なコードは以下となる。
初めてこのコードを見たときに何がなんだかさっぱりわからなかったので、学んだことをひとつひとつを解説していく。
# app/models/user.rb class User < ApplicationRecord has_many :active_relationships, class_name: 'Relationship', foreign_key: 'follower_id', dependent: :destroy has_many :passive_relationships, class_name: 'Relationship', foreign_key: 'followed_id', dependent: :destroy has_many :following, through: :active_relationships, source: :followed has_many :followers, through: :passive_relationships, source: :follower end
# app/models/relationship.rb class Relationship < ApplicationRecord belongs_to :follower, class_name: 'User' belongs_to :followed, class_name: 'User' end
事前に抑えて置きたいポイント
以下の4つを抑えて置くと関連付けのコードを理解しやすくなる。
has_many
とbelongs_to
の本質class_name
の意味とデフォルトの挙動foreign_key
の意味とデフォルトの挙動through
とsource
の挙動
has_many
とbelongs_to
の本質
has_many
とbelongs_to
の本質は「モデルのインスタンスが引数で指定した値をメソッドとして使えるようにする」こと。つまり、「メソッドを生成するメソッド」ということになる。
has_many
とbelongs_to
の違いは
- 「1:多」の関連付けをする1側のモデルに
has_many
を使い、引数は多側のモデル名の複数形にする - 「1:多」の関連付けをする多側のモデルに
belongs_to
を使い、引数は1側のモデル名の単数形にする(モデル名はもともと単数形)
例:よくある投稿機能のあるSNSアプリがあるとして、PostモデルとCommentモデルが1対多の関係であるとする。
# app/models/post.rb class Post < ApplicationRecord has_many :comments end
# app/models/comment.rb class Comment < ApplicationRecord belongs_to :post end
Postモデルにhas_many :comments
と書いた場合、Post.first.comments
、Post.first.comments.build
、Post.first.comments.find_by
などが使えるようになる。
また、Commentモデルにbelongs_to :post
と書くことで、Comment.first.post
、Comment.first.post.build
、Comment.first.post.find_by
などが同じく使えるようになる。
class_name
の意味とデフォルトの挙動
モデルの関連付けをする場合、デフォルトの挙動としてhas_many
やbelongs_to
の引数に指定したモデルクラス(モデル)に関連付けをしようとする。
例:
# app/models/post.rb class Post < ApplicationRecord has_many :comments # Commentモデルに関連付けされる end
# app/models/comment.rb class Comment < ApplicationRecord belongs_to :post # Postモデルに関連付けされる end
しかし、has_many
やbelongs_to
の引数に指定したモデル以外に関連付けをしたい場合もある。
その場合はclass_name
を使って引数にモデルクラス名(モデル名)を指定すれば良い。class_name: '関連付けしたいモデル名(単数形で頭文字を大文字にする)'
と記述する。
例:
# app/models/post.rb class Post < ApplicationRecord has_many :comments, class_name: 'Favorite' # Commentモデルではなくfavoriteモデルに関連付けされる end
# app/models/comment.rb class Comment < ApplicationRecord belongs_to :post, class_name: 'Like' # PostモデルではなくLikeモデルに関連付けされる end
foreign_key
の意味とデフォルトの挙動
前提:親モデルと子モデルの外部キーは同じものにする必要がある(外部キーのカラムを持つモデルが子モデル)。子モデルの外部キーを親モデルにも設定する。
has_many
を使う場合、デフォルトで#{自分のモデルクラス名}_id
が外部キーとして扱われる。
# app/models/post.rb class Post < ApplicationRecord has_many :comments # 外部キーはpost_id end
一方belongs_to
を使う場合は、#{belongs_toの引数}_id
がデフォルトで外部キーとして扱われる。
# app/models/comment.rb class Comment < ApplicationRecord belongs_to :post # 外部キーはpost_id end
# app/models/comment.rb class Comment < ApplicationRecord belongs_to :post, class_name: 'Like' # 外部キーlike_id end
外部キーをデフォルトものから別のものに指定したい場合は、foreign_key
を使用する。
書き方はforeign_key: #{外部キー名}
。
※デフォルトと違って末尾に自動で_id
とはならないので注意
# app/models/post.rb class Post < ApplicationRecord has_many :comments, foreign_key: 'user_id' # 外部キーをuser_idへ変更 end
# app/models/comment.rb class Comment < ApplicationRecord belongs_to :post, class_name: 'Like', foreign_key: 'comment_id' # 外部キーをuser_idへ変更 end
through
とsource
の挙動
through
は引数で指定したメソッドをモデルのインスタンスに対して実行し、そこで得られたデータの集合の1つ1つの要素に対してsource
で指定したメソッドを実行し、その結果を返す。
map
メソッドをしているイメージ。
フォロー機能の解説
# app/models/user.rb class User < ApplicationRecord # 外部キーをfollower_idに設定し、relationshipモデルと関連付け、データを取得できるactive_relationshipsというメソッドを生成 has_many :active_relationships, class_name: 'Relationship', foreign_key: 'follower_id', dependent: :destroy # active_relationshipsメソッドをuserモデルのインスタンス(ユーザ1やユーザ2など)に対して実行し、得られたデータの集合1つ1つのデータに対してfollowedメソッドを実行し(得られた1つ1つのデータはrelationshipモデルのインスタンスなのでfollowerメソッドが使える)、その結果を返すfollowingメソッドを作成 has_many :following, through: :active_relationships, source: :followed end
# app/models/relationship.rb class Relationship < ApplicationRecord # 外部キーをfollower_idに設定し、userモデルと関連付け、データを取得できるfollowerというメソッドを生成 belongs_to :follower, class_name: 'User' end