acts-as-taggable-onとRansackを使用したタグの複合検索


やりたいこと

表題の通り“acts-as-taggable-onとRansackを使用したタグの複合検索”
複合検索とは、スペース区切りのAND検索を指します。
今回は、映像コンテンツにtypeというタグを付けて管理するという例で考えてみましょう。
同じようなことをする方は、少ないと思われますが、検索しても意外と結果が出てこないので、今後の方のために。
複合検索したいという場合は、modelの実装をすっ飛ばして実装すれば問題なく実装できるはず。
すこしRESTfulでないPathですが、ご勘弁ください。

最終実装

app/models/Video.rb
class Video < ActiveRecord::Base
  acts_as_taggable_on :type
end
app/controller/searches.rb
class SearchesController < ApplicationController

  def video
    groupings = []  # 空配列生成

    # パラメーターがあれば分割し、複数のAND条件分を生成する
    if params[:q].present?
      keywords = params[:q][:type_name_cont].split(/[\p{blank}\s]+/)
      keywords.each { |value| groupings.push(type_name_cont: value) }
    end

    # 生成したAND条件を検索する。初期の場合、全件出力
    @query = Video.ransack(
        combinator: 'and',
        groupings: groupings
    )

    @videos = @query.result(distinct: true)

  end

end

検索用のErbは以下のような感じ。

app/view/searches/video.htmk.erb
<%= search_form_for @query, url: searches_video_path, method: :post do |f| %>

    <!-- 検索窓 -->
    <div class="form-group">
      <label>フリーワード検索</label><br />
      <%= f.search_field :type_name_cont,
                         class: 'form-control',
                         value: params.dig(:q, :type_name_cont) %>
      <small class="help-block">※スペースで区切ることで複合検索ができます</small>
    </div>

<% end %>
app/config/routes.rb
Rails.application.routes.draw do
  # 動画検索
  get 'searches/video' => 'searches#video'
  post 'searches/video' => 'searches#video'
end

ポイント1

ネット上の記事では、tags_nameとなっているものが多いが…
ここではmodelで指定したtypeがデータベースでcontextとして扱われるためRansackの検索では、type_nameと記述すること。
例えば、標準のままのacts_as_taggableだけの場合は、tags_nameとなる。

ポイント2

ANDの複合検索のためにparams[:q]を再構築する。
再構築したものをRansackに代入する。
combinatororにするとor検索となる

app/controller/searches.rb
    # パラメーターがあれば分割し、複数のAND条件分を生成する
    if params[:q].present?
      keywords = params[:q][:type_name_cont].split(/[\p{blank}\s]+/)
      keywords.each { |value| groupings.push(type_name_cont: value) }
    end

ポイント3

このままだと検索後に検索窓に結果が残らないという事態に遭遇するので、フォームのvalueparams.digを指定する

ポイント4

このままの実装だと、重複した結果が出力される可能性があるため、distinctを有効にする

最後に

ネット上のいろいろなサイトを潜りましたがなかなか良い回答が見つけにくかったので、残しておきます。
特にRansacを使用した複合検索をしたい方は、多いのではないでしょうか。