概要
Railsを使用してデータを扱う際、殆どこんな機会は訪れない(別の方法などで代替できることがほとんど)ですが、どうしよもない事情で中間テーブルから関連付けを貼る必要があったので備忘録としてメモっておきます。
事前知識
class User < ApplicationRecord
has_many :group_users
has_many :groups, through: :group_users
has_many :comments
end
class Group < ApplicationRecord
has_many :group_users
has_many :users, through: :group_users
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :user
belongs_to :group
end
class GroupUser < ApplicationRecord
belongs_to :group
belongs_to :user
end
このようなクラスが存在しているとします。
通常であればGroupUserという中間テーブルを起点として何かすることはほとんどないですし、あったとしてもUserやGroup経由でCommentを取得することができるので、困ることはほとんどありません。
一方、GraphQLを使用していたり、何かアソシエーションのタイミングで整ったデータが欲しいなどの稀な事情でGroupUserのインスタンスからgroupとuserが絞り込まれたcommentの一覧が欲しい場合があったりしました。
普通にthroughをして取得しようとすると、例えば下記のようなコードになります。
class GroupUser < ApplicationRecord
belongs_to :group
belongs_to :user
has_many :comments, through: :user #groupでもいい
end
ただ、この方法だとthroughしたどちらかのカラムの値では絞り込まれますが、もう片方のカラムでは絞り込みが行われません。
どちらでthroughしたとしても、userかgroupのどちらかでの絞り込みしか行われず、意図した値は取得できません。
解決策
じゃあどうするのかというと、socpeを使います。
class GroupUser < ApplicationRecord
belongs_to :group
belongs_to :user
has_many :comments,->(group_user) { where(group_id: group_user.group_id) }, through: :user #groupならuserでwhere
end
has_manyはスコープの引数として自分自身だけは取ることができます。なので、このようにすることでgroup_userインスタンスのgroup_idとuser_idを持ったコメントの一覧を取得することができます。
どうしても中間テーブルを起点にして取得しなければいけない状況になった場合に役立てば幸いです。