Railsチュートリアルの14章で出てきた**「has_many through」**の使い方をわかりやすいように、まとめていきます。
※Railsチュートリアルをやっていないと、いきなりコードを見ても理解できないかもしれないです。
まず、以下のコードを例に説明していきます。
class User < ApplicationRecord
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :followeds, through: :active_relationships #ここ
#<User id, name, email, created_at, updated_at, password_digest>
end
class Relationship < ApplicationRecord
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
#<Relationship: id, follower_id, followed_id, created_at, updated_at>
end
throuchの挙動は、
・userモデルのインスタンスにthroughの後に書かれたメソッドを実行する。
・・戻り値のrelationshipインスタンスにhas manyの第一引数を単数系にした、メソッドをインスタンス一つ一つに実行する
↑この説明じゃわからないと思うので、もう少しわかりやすく説明していきます。
まず**「・userモデルのインスタンスにthroughの後に書かれたメソッドを実行する。」**についてです。
has_many :followeds, through: :active_relationships
userモデルに対して「through: :active_relationships」に書かれている「:active_relationships」メソッドを実行します。
↓つまり内部でこんな感じに動きます。
user = User.first #仮に最初のユーザーだったら
relationships = user.active_relationships
active_relationshipsメソッドは
user.id == relationship.follower_idのrelationshipインスタンスの集合を返すので
relationships変数の中には、relationshipインスタンスの集合が入っています。
次に**「・戻り値のrelationshipインスタンスにhas manyの第一引数を単数系にした、メソッドをインスタンス一つ一つに実行する」**の部分です。
has_many :followeds, through: :active_relationships
has_manyの第一引数の:followedsを単数系にするとfollowedになり、
先ほどのrelationship変数に対してfollowedメソッドを実行します。
followedメソッドはRelationshipモデル内のbelongs_toで生成されたメソッドです。
belongs_to :followed, class_name: "User"
↓内部ではこうなっています。
user = User.first#仮に最初のユーザーだったら
relationships = user.active_relationships
relationships.map(&:followed)
この一連の流れを「has_many :followeds, through: :active_relationships」で生成した。followedsメソッドをuserインスタンスに対して呼び出すと実行してくれます。
###第一引数を任意の単語に変更することもできます。
:followedsを :followingに変更します。
ですがこれではfollowed_idと紐ずくuserインスタンスを取得できません。
そのため明示的にsource: :followedとしてfollowed_idと紐ずくuserインスタンスが欲しいとrailsに伝える必要があります。
class User < ApplicationRecord
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
end
##まとめると
user.following
と書くと内部では、
user.active_relationships.map(&:followed)
このような処理が行われています。
※厳密に言うとmapを使うと戻り値は配列で、user.followingを使うと戻り値のクラスは「User::ActiveRecord_Associations_CollectionProxy」になるみたいですが、データの集合が取得できるという意味では近いイメージです。