Help us understand the problem. What is going on with this article?

Ransackのススメ

More than 5 years have passed since last update.

Railsアプリで検索機能を実装するケースは非常に多いと思います。
簡単な検索であればwhereとLIKEを使って書けますし、やや複雑なものもeverywhereが便利ですが、ここではもっと複雑な条件の組み合わせを実装する時に便利なransackを紹介します。

基本

searchメソッドで条件を指定し、resultメソッドで結果を返します。

Item.search(:name_cont => 'ほげ').result

resutはActiveRecord::Relationを返すので、SQLは普通のActiveRecord同様遅延評価されますし、さらにwhereを繋げたり、kaminariでページングしたりすることもできます。また、to_sqlで発行されるSQLを確認することもできます。

もう少し詳しく書くと、searchはModelまたはActiveRecord::RelationをレシーバにしてRansack:Searchを返し、Ransack:search#resultはActiveRecord::Relationを返します。

searchの書き方

searchメソッドは一種のDSLになっています。
属性をpredicate(述語)で繋いだ文字列のシンボルをキーに、検索対象を値にして書いていきます。
前述の例だと「name属性に'ほげ'という文字列を含む(countain)」対象を検索します。以下のようなSQLが発行されます。

Item.search(:name_cont => 'ほげ').result.to_sql
# => "SELECT `items`.* FROM `items` WHERE `items`.`name` LIKE '%ほげ%')"

以下のようなpredicateがあります。

eq

等しいものにマッチします。

Item.search(:name_eq => 'ほげ').result.to_sql
# => "SELECT `items`.* FROM `items` WHERE `items`.`name` = 'ほげ')"

単体だとwhereで十分なので意味がありませんが、複数の条件を動的に組み合わせる際に便利です。
not_eqで「等しくないもの」になります。

lt

ある値より小さいものにマッチします。

Item.search(:price_lt => 1000).result.to_sql
# => "SELECT `items`.* FROM `items` WHERE `items`.`price` < 1000)"

lteqだと「以下」になります。

gt

ある値より大きいものにマッチします。

Item.search(:price_gt => 1000).result.to_sql
# => "SELECT `items`.* FROM `items` WHERE `items`.`price` > 1000)"

gteqだと「以上」になります。

in

SQLのinです。含まれるものにマッチします。

Item.search(:category_id_in => [5,10,15,20]).result.to_sql
# => "SELECT `items`.* FROM `items` WHERE `items`.`category_id` IN (5,10,15,20))"

not_inで「含まれないもの」になります。

cont

前述したように「文字列が含まれるもの」にマッチします。
not_contで「文字列が含まれないもの」になります。

start

「特定の文字列が先頭のもの」にマッチします。

Item.search(:name_start => 'ほげ').result.to_sql
# => "SELECT `items`.* FROM `items` WHERE `items`.`name` LIKE 'ほげ%')"

endで「特定の文字列が末尾のもの」になります。

predicateはこの他にもあるので、wikiを参考にするといいでしょう。

組み合わせ

複数の属性を組み合わせることができます。
その場合、andかorで繋ぎます。

Item.search(:name_and_description_cont => 'ほげ').result.to_sql
# => "SELECT `items`.* FROM `items` WHERE (((`items`.`name` LIKE '%ほげ%') AND (`items`.`description` LIKE '%ほげ%')))"
Item.search(:name_or_description_cont => 'ほげ').result.to_sql
# => "SELECT `items`.* FROM `items` WHERE (((`items`.`name` LIKE '%ほげ%') OR (`items`.`description` LIKE '%ほげ%')))"

複数条件

条件を組み合わせることができます。
末尾に_allを付けると全ての条件にマッチ、_anyだといずれかにマッチするものを検索します。

Item.search(:name_cont_all => ['ほげ', 'ひげ']).result.to_sql
# => "SELECT `items`.* FROM `items` WHERE (((`items`.`name` LIKE '%ほげ%') AND (`items`.`name` LIKE '%ひげ%')))"
Item.search(:name_cont_any => ['ほげ', 'ひげ']).result.to_sql
# => "SELECT `items`.* FROM `items` WHERE (((`items`.`name` LIKE '%ほげ%') OR (`items`.`name` LIKE '%ほげ%')))"

関連モデルの検索

関連先を条件に含めることができます。

Item.search(:comments_body_cont => 'ほげ').result.to_sql
# => "SELECT `items`.* FROM `items` LEFT OUTER JOIN `item_comments` ON `item_comments`.`item_id` = `items`.`id` WHERE `item_comments`.`body` LIKE '%ほげ%'"

注意点

存在しないカラムを指定すると例外を返さず無視されます。

Item.search(:unknownattribute_cont => 'ほげ').result.to_sql
# => "SELECT `items`.* FROM `items`"

動的に組み立てる際には便利ですが、実装せずにテストが通ってしまったりするのでテストコードにマッチ・アンマッチ双方のテストを書くなど工夫しましょう。

nysalor
Rubyで仕事しています。宗派はEmacs教矢印キー派です。
http://blog.larus.jp/
r-n-i
二つのアプリ・サービス、CODE、Mycomment を開発・運営しています。ユーザーに対してはポイントが貯まる家計簿・ポイントが貯まる調査アンケートを提供し、顧客企業に対してはインターネットを活用したマーケティングリサーチ、販売促進プロモーションを提供しています。
https://r-n-i.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away