Ruby
Rails

ransackを使って深いアソシエーションに対するクエリを発行する

はじめに

ransackのsimple modeではアソシエーションを使った検索も簡単に発行できます。
実際にgithubを見てみるとこんな感じで説明してます。
この例だと1階層、つまり直接関連のあるモデルに対してのPredicatesが紹介されています。が、2階層以上先のモデルに対しての Predicates はどうすれば良いでしょう?

直接関連していない(深い)アソシエーションへのクエリ

例えば以下のようなモデルがあったとします。

class User
  has_many :posts
  attribute :email, :string
end

class Post
  has_many :comments
  belongs_to :user
  attribute :title, :string
end

class Comment
  belongs_to :post
  attribute :body, :text
end

そしてこんなcontrollerがあったとしましょう。

class UsersController
  def index
    @q = User.includes(posts: :comments).ransack(params[:q])
    @users = @q.result(distinct: true)
  end
end

そうするとview側ではf.search_field :comments_title_contのようなPredicatesは書くことができます。これはとても便利です。であればと思いf.search_field :comments_body_contのようなPredicatesも書けるのではないかと思い試してみましたが、これは誤りでした。実際にKlass.ransackable_associationsを叩いてみると分かります。

User.ransackable_associations
# => ['posts']

このメソッドでは直接関連付いているものしか表示されないことが分かります。なので上記の例ではUserhas_xxx through関連を設定してあげることでransackable_associationsに追加することもできます。もしくはTable Viewを作って一つのARクラスとして表現してしまうという方法も取れるかと思います。
ただ、他に目的もなくransackのためにわざわざ用意するのはちょっと手間ですし、もしransackを使わなかったら必要がないようなことだったのであればせっかくライブラリを使ってるのに労力の面ではあまり報われないですよね。

実はドキュメントにはなかったのですが深いアソシエーションに対応するPredicatesはすでに用意されていました。

# app/views/users/index.slim

search_form_for @q do |f|
  # f.search_field :comments_body_cont
  # => undefined method `comments_body_cont' for #<Ransack::Search:0x007f8abf50a6d0>
  f.search_field :posts_comments_body_cont #=> ok!!

上記のようにリレーション名を繋げることで目的のリレーションまでたどることができます。これを使えば深いアソシエーションにも対応することができますね :)

久しぶりに使ってみてこれってどうやるんだっけ?と調べていてすぐに情報にアクセスできなかったため投稿してみました。誰かのお役にたてれば幸いです。