polymophicなテーブルを経由して、has_many thourghする。

2018年9月2日

 

Railsのアソシエーションで、中間テーブルを経由して一個飛ばしでインスタンスを取得できるhas_many~through便利ですよね。ところが、この中間テーブルがpolymophicだった場合に、どうやって対象を取得すれば良いのかということに悩みました。

 

解決策は、結論からいうと、下の例でいうと
モデルにはこういう形で定義して、そのままuser.post_commentsのような形でできます。

 

 

has_many :post_comments, through: :comments, :source => :commentable,:source_type => 'Post'
has_many :blog_comments, through: :comments :source => :commentable,:source_type => 'Blog'

 

 

図やコードを交えて解説

 

これだけではなんのことかよくわからないかもしれないので、上の結論のコードの全容を明かすと以下のようになります。

以下のモデルは、複数のユーザがブログを作り、各々ポストを投げていくという世界を想定したモデル群です。

has_many~through polymophioc

 

 

class User < ActiveRecord::Base 
  has_many :blogs 
  has_many :posts 
  has_many :comments 
  has_many :post_comments, through: :comments, :source => :commentable,:source_type => 'Post'
  has_many :blog_comments, through: :comments :source => :commentable,:source_type => 'Blog'
end

class Blog < ActiveRecord::Base
  has_many :comments, as: :commentable
end

class Post < ActiveRecord::Base
  has_many :comments, as: :commentable
end

class Comment < ActiveRecord::Base
  belongs_to :commentables, polymophic: true
end

 

コードを見てみると、確かにいつものようにhas_manyで「Userが行った投稿を取ってくる」ことや「Userが持っているブログを取得する」ということは簡単にできます。ただ、「ユーザーがコメントした投稿」「ユーザーがコメントしたブログ」についてはどうやって取得しようか少し迷うところがあると思います。

 

 

特に、ここでは、中間テーブルの役割をはたすCommentがポリモーフィック関連なのでなおさらです。

railsのポリモーフィック関連では、~able_idと~able_typeというカラムで関連が定義されますが、上のコードを例にとるとhas_many :commentsだけでは、「ユーザーがコメントした投稿」と「ユーザーがコメントしたブログ」の両方ともが取得されてきてしまいます。愚直にやるのであれば、commentable_typeを条件にSQLを発行してとやればできなくはないですが、それは苦しいです。。

 

 

そこで、この解決策では、:sourceオプションと:source_typeオプションを使って、ポリモーフィック関連の先のテーブルを指定してあげることで「ユーザーがコメントした投稿」と「ユーザーがコメントしたブログ」を簡単に取得できるようにしています。

 

polymophicなテーブルを経由して、has_many thourghする場合は、sourceオプションでポリモーフィック関連の名前を指定して、source_typeで中間テーブルの先のクラスを指定してシンプルにデータをとってきましょう。