タイトルからしてややこしいんですが。
"milk corn"で検索するとこういうSQLを発行したい。
SELECT
"users".*
FROM
"users"
WHERE
(
(name ILIKE '%milk%' OR email ILIKE '%milk%') AND
(name ILIKE '%corn%' OR email ILIKE '%corn%')
)
移行元システムのCGI.pmにベタ書きで実装されてるSQLがこのようになっていて新システムのUXが落ちてると指摘された。
検索フィールドが5個も10個もあるのはイヤみたい。(今後、開始終了年月日とかで絞り込みたいとか言われそうなのでRansack使えないとそれはそれで辛い)
Ransackのcont_all, cont_anyだと想定しているSQLにならない。
scope.mergeやAdvancedSearchで検索自体はできるけどparams[:q]を書き換えてるので検索フォーム側とattributesの整合が取れずキーワードを維持できなかったりする。
例えば、name_or_email_contをname_cont, email_contのようにして@q = Ransack.search(search_query)する。
searchkick(+elasticsearch)なんかも試しだしてこれは盛大に脱線してる、namazuみたいな感じでこれはこれで狙った検索結果にならなかった。
Ransackなしで実装できるけど別テーブルのカラム検索とかsort_linkとかの面倒みないといけないのかと悶々とする。
add_predicateとransackerを使って対応した。
Userモデルはname, emailカラムだけあるとします。
name_or_email_cont_all
puts User.ransack(name_or_email_cont_all: "milk corn".split).result.to_sql
SELECT
"users".*
FROM
"users"
WHERE
(
("users"."name" ILIKE '%milk%' AND "users"."name" ILIKE '%corn%') OR
("users"."email" ILIKE '%milk%' AND "users"."email" ILIKE '%corn%')
)
name_or_email_cont_any
puts User.ransack(name_or_email_cont_any: "milk corn".split).result.to_sql
SELECT
"users".*
FROM
"users"
WHERE
(
("users"."name" ILIKE '%milk%' OR "users"."name" ILIKE '%corn%') OR
("users"."email" ILIKE '%milk%' OR "users"."email" ILIKE '%corn%')
)
Ransackで簡単に検索フォームを作る73のレシピ - 猫Rails
「045 半角スペース区切りの文字列で検索する」のとおりに述語(predicate)を追加する
config/initializers/ransack.rb
Ransack.configure do |config|
config.add_predicate 'has_every_term',
arel_predicate: 'matches_all',
formatter: proc {|v| v.split.map {|t| "%#{t}%"}}
end
name_or_email_has_every_term(cont_all相当)
puts User.ransack(name_or_email_has_every_term: "milk corn").result.to_sql
SELECT
"users".*
FROM
"users"
WHERE
(
("users"."name" ILIKE '%milk%' AND "users"."name" ILIKE '%corn%') OR
("users"."email" ILIKE '%milk%' AND "users"."email" ILIKE '%corn%')
)
Ransackで簡単に検索フォームを作る73のレシピ - 猫Rails
「049 full_nameを検索する」のとおりに対象となるカラムをransackerで定義する
app/models/users.rb
ransacker :search_fields do
Arel.sql('CONCAT(name, email)')
end
search_fields_has_every_term
puts User.ransack(search_fields_has_every_term: "milk corn").result.to_sql
SELECT
"users".*
FROM
"users"
WHERE
(
CONCAT(name, email) ILIKE '%milk%' AND
CONCAT(name, email) ILIKE '%corn%'
)
別テーブル(Product.name)も検索したい場合
app/models/users.rb
ransacker :search_fields do
Arel.sql('CONCAT(name, email, products.name)')
end
でも、これはこれでCONCATしてるからindex使えてないのが辛い。
あとArel.sql('CONCAT(name, ' ', email, ' ', products.name)')のように半角スペースを挟み込まないと予期しない検索結果になりそうではある。