3
3

More than 3 years have passed since last update.

ransackの複数ワード検索、複数モデル検索、複数タグ検索(acts-as-taggable-on)の実装

Posted at

ransackの複数モデル検索、複数タグ検索に少してこずったので、同じような人の参考になればと思って投稿します。

今回はUserモデル、Articleモデル、Tagモデル(acts-as-taggable-onっていうgemを使用)のケースで実装します。。投稿された記事のarticleのtextカラム、Userのnameカラム、そしてUserに紐づいているtagのnameカラムから(複数ワード)検索を行うというものです。
(今回はtagはUserに紐づいていてarticleには紐づいていません)

はじめにview側ですが、下記のようになります

articles/index.html.erb
<%= search_form_for(@search, url: articles_path) do |f| %>
  <%= f.search_field :text_or_user_name_cont_all  , class:"search_field", placeholder:"検索" %>
  <%= f.submit '検索', id:"search_button" %>
<% end %>

このsearch_form_forやsearch_field、cont_allはransakの書き方となります。
この辺りの説明は省きます。

controller側ではransackのスコープ機能(ransackable_scopes)を呼び出しています。
引数で検索ワードを渡してarticleのmodel側で検索しています。

articles_controller.rb
   .
   .
@search = Article.ransack(params[:q])
  if params[:q].present? #paramsのnil対策
    @search_articles_result = Article.by_any_texts(params[:q][:text_or_user_name_cont_all])
  end
   .
   .
article.model.rb
scope :by_any_texts, -> (string){
  words = string.split(/[\p{blank}\s]+/)
  searchs = search(text_or_user_name_cont_all: words).result(distinct: true)
  search_tag = User.by_any_tag(words).flatten
  search_result = searchs + search_tag
  search_result.uniq
}

def self.ransackable_scopes(auth_object = nil)
  %i[text_or_user_name_cont_all]
end

引数で渡されたparams[:q][:text_or_user_name_cont_all: words]をsplitして["word1","words2"]という形にします。
そのあとransackしますが、ransackのモデルを跨いでの検索はアソシエーションの記述感覚で簡単に実装できます。
今回の場合だとArticleのtextとUserのnameを検索したい場合は、text_or_user_nameとします。
書き方としては、関連するモデル名(user)+カラム名(name)で実装できます。
さらにuserに紐づいているtagも調べたいときはtext_or_user_name_or_user_tags_nameで検索できます。
tagsと複数形になるのはuserとtagが1対多の関係にあるためです。
ただ、今回のtag機能の実装はacts-as-taggable-onのgemを利用したためだと思いますが、複数のワード検索をしたときに引っ張ってきませんでした。(一単語だと引っ張ってきましたが複数ワードで検索するとダメでした、、、)
そこでtagに関しては別途Userモデルにscopeを作り、acts-as-taggable-onの検索機能(tagged_with)を使って実装しました。

user.model.rb

scope :by_any_tag, -> (words){
  user_tag = User.tagged_with(words, named_like_any: true)
  user_tag.map{|tag| tag.articles}         
}

article側でsplitしたwordsを引数として受け取ってUserに紐づいているタグを検索します。
その後にmapメソッドで検索結果に紐づいているarticleの配列を新しく作っています。

article側ではby_any_tagを呼び出して、二次元配列になっているのをflattenして、元々のtext_or_user_name_cont_allの検索結果とuser側で検索したtagの検索結果を足し合わせています。重複している結果があるかもしれないのでuniqして終了です。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3