607
580

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ransackのススメ

Last updated at Posted at 2012-10-12

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`"

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

607
580
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
607
580

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?