Rails

【Ransack翻訳】Railsで検索機能を簡単に実装できるgem「Ransack」のREADMEを翻訳してみた

Rails

 

今回は、Railsで検索機能を簡単に実装できるgem「Ransack」のREADMEを翻訳してみました。Ransackの知識が曖昧な方、Ransackについて深掘りしたい方の参考になればと思います。

この記事は2020/11/28現在の翻訳になります。

Github

原文はこちらを参照してください。

Ransack

Ransack
Build Status Gem Version Code Climate Backers on Open Collective Sponsors on Open Collective

Ransackを使用することで、Ruby on Railsアプリケーションの単純な検索フォームと高度な検索フォームの両方を作成できます(デモソースコードはこちら)。モデルまたはコントローラーレイヤーでのクエリ生成を簡素化するものを探している場合は、Ransack(またはMetaSearch)はふさわしくありません。代わりにSqueelを試しましょう。

始め方

Ransackは、Ruby2.3以降のRails6.0、5.0、5.1、および5.2と互換性があります。

最後に公式にリリースされたgemを使用したい場合は、Gemfileに以下の記述を:

gem 'ransack'

 

最新のアップデートされたもの(推奨)を使用する場合は、masterブランチを使用してください。

gem 'ransack', github: 'activerecord-hackery/ransack'

イシュートラッカー

  • イシューを作成する前に、コントリビューティングガイドに目を通してください。
  • Ransackによって新たなバグ(まだ報告されていないもの)が発生した場合は、イシューを作成してください。
  • コントリビューションは歓迎です。しかし、イシューに”+1″のコメントやプルリクエストを送ることはしないでください😃
  • 個人のサポートを要求するためにイシュートラッカーは使用しないでください。Stack Overflowがあなたをサポートしてくれる良い場です。

使い方

Ransackは、シンプルモードとアドバンストモードの2つのモードを使用できます。

シンプルモード

このモードはメタサーチのような動きをし、とても少ない労力でセットアップすることができます。

シンプルモードを使用する場合の注意点としては以下です。

  1. デフォルトで送られるparamsのキーは「:search」ではなく「:q」です。これは主にクエリの文字列を短くするためですが、高度なクエリ(下記)はほとんどのブラウザでURLの長さの制限に違反し、HTTP POSTリクエストへの切り替えが必要です。このキーは設定可能です。
  2. form_forはsearch_form_forになり、Ransack::Searchオブジェクトが渡されたことを検証します。
  3. 一般的なActiveRecord::Relationメソッドは、検索オブジェクトによって処理を任せることはなくなりました。代わりにRansack#resultを呼び出すことで検索結果(ActiveRecordアダプターの場合はActiveRecord::Relation)を取得します。

 

コントローラー

def index
  @q = Person.ransack(params[:q])
  @people = @q.result(distinct: true)
end

 

関連付けられたテーブルのカラム(この例では、各々の記事とページネーションをpreloadします)でソートしたい場合はdistinct: trueを記述しません。

preloadとは、この先必要になるデータなどを予め読み込んでおき、読み込みにかかる時間を短縮する仕組みのこと。
def index
  @q = Person.ransack(params[:q])
  @people = @q.result.includes(:articles).page(params[:page])

  # or use `to_a.uniq` to remove duplicates (can also be done in the view):
  @people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
end

 

ビュー

2つの主要なRansackのビューヘルパーはsearch_form_forとsort_linkで、Ransack::Helpers::FormHelperに定義されています。

検索フォームを作成するには、form_forがRansackのsearch_form_forに置き換わります。

<%= search_form_for @q do |f| %>

  # Search if the name field contains...
  <%= f.label :name_cont %>
  <%= f.search_field :name_cont %>

  # Search if an associated articles.title starts with...
  <%= f.label :articles_title_start %>
  <%= f.search_field :articles_title_start %>

  # Attributes may be chained. Search multiple attributes for one value...
  <%= f.label :name_or_description_or_email_or_articles_title_cont %>
  <%= f.search_field :name_or_description_or_email_or_articles_title_cont %>

  <%= f.submit %>
<% end %>

 

f.search_fieldの引数は、「attribute_name[_or_attribute_name]…_ predicate」の形式にする必要があります。加えて「[_or_another_attribute_name]…」は_or_とname属性の繰り返しを意味しています。

cont(contains)とstart(start with)の2つは、利用可能な検索述語です。

全ての詳細はConstrantswikiを参照してください。

search_form_forの出力形式は次のように設定されます。

<%= search_form_for(@q, format: :pdf) do |f| %>

<%= search_form_for(@q, format: :json) do |f| %>

 

Ransackのsort_linkヘルパーは、分類可能なリンクであるテーブルヘッダーを作成します。

<%= sort_link(@q, :name) %>

 

異なるタイトルのカラムやデフォルトの並び替え順など、追加のオプションをカラム属性の後に渡すことができます。

<%= sort_link(@q, :name, 'Last Name', default_order: :desc) %>

 

リンクのマークアップがラベルパラメーターにフィットしにくい場合はブロックを使用することができます。

<%= sort_link(@q, :name) do %>
  <strong>Player Name</strong>
<% end %>

 

ポリモーフィックなアソシエーションでは「uninitialized constant Model::Xxxable error」を回避するため、リンク名を明示的に示す必要があります。

<%= sort_link(@q, :xxxable_of_Ymodel_type_some_attribute, 'Attribute Name') %>

 

順序付けられた配列を指定することで、複数のフィールドに並べ替えることも可能です。

<%= sort_link(@q, :last_name, [:last_name, 'first_name asc'], 'Last Name') %>

 

上記の例では、リンクをクリックすると、last_name、first_nameの順に並べ替えられます。配列内のフィールドに並べ替え方向を指定すると、Ransackは常にその特定のフィールドを指定された方向に並べ替えるようになります。

複数のdefault_orderフィールドをハッシュで指定することもできます。

<%= sort_link(@q, :last_name, %i(last_name first_name),
  default_order: { last_name: 'asc', first_name: 'desc' }) %>

 

この例では、両方のフィールドの並べ替え方向を切り替えます。デフォルトでは、最初はlast_nameフィールドを昇順で並べ替え、first_nameフィールドを降順で並べ替えます。

SQL関数の結果など、複雑な値で並べ替える場合は、スコープを使用して並べ替えることができます。次のように、モデルで並べ替える仮想フィールドの名前と名前が一致するスコープを定義します。

class Person < ActiveRecord::Base
  scope :sort_by_reverse_name_asc, lambda { order("REVERSE(name) ASC") }
  scope :sort_by_reverse_name_desc, lambda { order("REVERSE(name) DESC") }
...

 

続いて、この仮想フィールドを並べ替えることができます。

<%= sort_link(@q, :reverse_name) %>

 

ソートリンク順序インジケーターの矢印は、config/initializers/ransack.rbなどの初期化ファイルでcustom_arrowsオプションを設定することでグローバルにカスタマイズできます。

現在ソートに使用されていないすべてのソート可能なフィールドに表示されるdefault_arrowを有効にすることもできます。これはデフォルトで無効になっているため、何も表示されません。

Ransack.configure do |c|
  c.custom_arrows = {
    up_arrow: '<i class="custom-up-arrow-icon"></i>',
    down_arrow: 'U+02193',
    default_arrow: '<i class="default-arrow-icon"></i>'
  }
end

 

イニシャライザファイルでhide_sort_order_indicatorsをtrueに設定すると、すべてのソートリンクが順序インジケータの矢印なしで表示される場合があります。カスタマイズされている場合でも、矢印が非表示になることに注意してください。

Ransack.configure do |c|
  c.hide_sort_order_indicators = true
end

 

グローバルに設定せずに並べ替えリンクでhide_indicator: trueを渡すことにより、順序インジケーターの矢印なしで個々の並べ替えリンクを表示できます。

<%= sort_link(@q, :name, hide_indicator: true) %>

 

Ransackのsort_urlヘルパーはsort_linkに似ていますが、URLのみを返します。

sort_urlにはsort_linkと同じAPIがあります。

<%= sort_url(@q, :name, default_order: :desc) %>
<%= sort_url(@q, :last_name, [:last_name, 'first_name asc']) %>
<%= sort_url(@q, :last_name, %i(last_name first_name),
  default_order: { last_name: 'asc', first_name: 'desc' }) %>

アドバンスモード

「高度な」検索は、ネストされたAND/ORグループ化などを使用して複雑なクエリを生成するために、Railsのネストされた属性機能を使用します。これには少し手間がかかりますが、非常に優れた検索インターフェイスを生成できます。これらの検索の注目すべき欠点は、パラメータ文字列のサイズが大きくなると、通常、GETの代わりにHTTP POSTメソッドを使用する必要があるということです。

これは、routesを微調整する必要があることを意味します…

resources :people do
  collection do
    match 'search' => 'people#search', via: [:get, :post], as: :search
  end
end

 

…そして別のコントローラーアクションを追加します…

def search
  index
  render :index
end

 

…そしてビューのsearch_form_for行を更新します…

<%= search_form_for @q, url: search_people_path,
                        html: { method: :post } do |f| %>

 

これらを行うことでRansack::Helpers::FormBuilderのヘルパーを使用し、デモアプリここのソースコード)にあるような、はるかに複雑な検索フォームを作成することができます。

Ransack searchメソッド

Ransackは、クラスメソッド#searchをモデルで使用できるようにしようとしますが、#searchがすでに他の場所で定義されている場合は、いつでもデフォルトの#ransackクラスメソッドを使用することができます。したがって、以下は同等です。

Article.ransack(params[:q])
Article.search(params[:q])

 

ユーザーから#search名が他のgemと競合する問題が報告されているため、#searchメソッドエイリアスはRansack(2.0)の次のメジャーバージョンで非推奨になります。代わりに、デフォルトの#ransackを使用することをお勧めします。

今のところ、Ransackの#searchメソッドが、コード内のsearchという名前の別のメソッドまたは別のgemの名前と競合する場合は、Ransack::Adapters::ActiveRecord::Baseの拡張class_methodにパッチを適用して、行エイリアスを削除することで解決できます。 またはconfig/initializers/ransack.rbにあるRansack初期化ファイルに次の行を配置します。

Ransack::Adapters::ActiveRecord::Base.class_eval('remove_method :search')

アソシエーション

Ransackを使用して、has_manyおよびbelongs_toアソシエーション内のオブジェクトを簡単に検索できます。

これらの関連付けを考えると…

class Employee < ActiveRecord::Base
  belongs_to :supervisor

  # has attributes first_name:string and last_name:string
end

class Department < ActiveRecord::Base
  has_many :supervisors

  # has attribute title:string
end

class Supervisor < ActiveRecord::Base
  belongs_to :department
  has_many :employees

  # has attribute last_name:string
end

 

…そしてコントローラー…

class SupervisorsController < ApplicationController
  def index
    @q = Supervisor.ransack(params[:q])
    @supervisors = @q.result.includes(:department, :employees)
  end
end

 

…このようにフォームを設定するかもしれません…

<%= search_form_for @q do |f| %>
  <%= f.label :last_name_cont %>
  <%= f.search_field :last_name_cont %>

  <%= f.label :department_title_cont %>
  <%= f.search_field :department_title_cont %>

  <%= f.label :employees_first_name_or_employees_last_name_cont %>
  <%= f.search_field :employees_first_name_or_employees_last_name_cont %>

  <%= f.submit "search" %>
<% end %>
...
<%= content_tag :table do %>
  <%= content_tag :th, sort_link(@q, :last_name) %>
  <%= content_tag :th, sort_link(@q, :department_title) %>
  <%= content_tag :th, sort_link(@q, :employees_last_name) %>
<% end %>

 

関連付けの並べ替えに問題がある場合は、記号化された関連付け(:department_title、:employees_last_name)の代わりに、複数形のテーブル( ‘departments.title’、 ‘employees.last_name’)でSQL文字列を使用してみてください。

Ransack エイリアス

ransack_aliasを使用して、Ransack検索の属性名をカスタマイズすることができます。これは関連付けや複数のカラムをクエリする際、長い属性名でなければならないといった場面に特に役立ちます。

class Post < ActiveRecord::Base
  belongs_to :author

  # Abbreviate :author_first_name_or_author_last_name to :author
  ransack_alias :author, :author_first_name_or_author_last_name
end

 

これで、フォームで:author_first_name_or_author_last_name_contを使用する代わりに、:author_contを使用できます。これは、URLでより表現力豊かなクエリパラメータを生成するのに役立ちます。

<%= search_form_for @q do |f| %>
  <%= f.label :author_cont %>
  <%= f.search_field :author_cont %>
<% end %>

検索マッチャ

可能なすべての述語のリスト

述語説明注釈
*_eqequal
*_not_eqnot equal
*_matchesmatches with LIKEe.g. q[email_matches]=%@gmail.com
*_does_not_matchdoes not match with LIKE
*_matches_anyMatches any
*_matches_allMatches all
*_does_not_match_anyDoes not match any
*_does_not_match_allDoes not match all
*_ltless than
*_lteqless than or equal
*_gtgreater than
*_gteqgreater than or equal
*_presentnot null and not emptyOnly compatible with string columns. Example: q[name_present]=1 (SQL: col is not null AND col != '')
*_blankis null or empty.(SQL: col is null OR col = '')
*_nullis null
*_not_nullis not null
*_inmatch any values in arraye.g. q[name_in][]=Alice&q[name_in][]=Bob
*_not_inmatch none of values in array
*_lt_anyLess than anySQL: col < value1 OR col < value2
*_lteq_anyLess than or equal to any
*_gt_anyGreater than any
*_gteq_anyGreater than or equal to any
*_lt_allLess than allSQL: col < value1 AND col < value2
*_lteq_allLess than or equal to all
*_gt_allGreater than all
*_gteq_allGreater than or equal to all
*_not_eq_allnone of values in a set
*_startStarts withSQL: col LIKE 'value%'
*_not_startDoes not start with
*_start_anyStarts with any of
*_start_allStarts with all of
*_not_start_anyDoes not start with any of
*_not_start_allDoes not start with all of
*_endEnds withSQL: col LIKE '%value'
*_not_endDoes not end with
*_end_anyEnds with any of
*_end_allEnds with all of
*_not_end_any
*_not_end_all
*_contContains valueuses LIKE
*_cont_anyContains any of
*_cont_allContains all of
*_not_contDoes not contain
*_not_cont_anyDoes not contain any of
*_not_cont_allDoes not contain all of
*_i_contContains value with case insensitiveuses LIKE
*_i_cont_anyContains any of values with case insensitive
*_i_cont_allContains all of values with case insensitive
*_not_i_contDoes not contain with case insensitive
*_not_i_cont_anyDoes not contain any of values with case insensitive
*_not_i_cont_allDoes not contain all of values with case insensitive
*_trueis true
*_falseis false

(全リストを見る: https://github.com/activerecord-hackery/ransack/blob/master/lib/ransack/locale/en.yml#L15 and wiki

Ransackersを使用してArel経由でカスタム検索機能を追加する

Ransackの背後にある主な前提は、Arel述語メソッドへのアクセスを提供することです。 Ransackは、Arelを介して追加の検索関数を作成するために、ransackersと呼ばれる特別なメソッドを提供します。 ransackerメソッドの詳細については、wikiを参照してください。実用的なランサッカーのコード例をwikiに投稿してください!

DISTINCT選択の問題

distinct :trueが渡された場合、結合の条件によって何らかの結果が生じる場合でも、結果は重複行を返さないようにSELECT DISTINCTを生成します。リレーションでuniqを呼び出すのと同じSQLを生成します。

多くのデータベースでは、関連付けられたテーブルのカラムを並べ替えると、異なるSQLがtrueになる可能性があることに注意してください。そのような場合、これらのクエリを機能させるには、必要に応じて結果を変更する必要があります。

たとえば、joins and includeを呼び出して結果に含めると、次のように、これらのテーブルのカラムをselectステートメントに追加して問題を解決できます。

def index
  @q = Person.ransack(params[:q])
  @people = @q.result(distinct: true)
              .includes(:articles)
              .joins(:articles)
              .page(params[:page])
end

 

上記が役に立たない場合は、ActiveRecordのselectクエリを使用して、必要なカラムを明示的に追加することもできます。これにより、要改善のSQLエンジン、かつ必要なカラムが強制的に追加されるため、すべてのカラムを指定する必要があります。

例えば:

def index
  @q = Person.ransack(params[:q])
  @people = @q.result(distinct: true)
              .select('people.*, articles.name, articles.description')
              .page(params[:page])
end

 

Postgresqlを使用する際にアプローチする別の方法は、ActiveRecordsの.includesをdistinct :trueではなく.groupと組み合わせて使用​​することです。

例えば:

def index
  @q = Person.ransack(params[:q])
  @people = @q.result
              .group('persons.id')
              .includes(:articles)
              .page(params[:page])
end

 

最後の方法は、コードの最後にコレクションでto_a.uniqを呼び出すことです。ただし、重複排除はSQLではなくRubyで行われるため、速度が低下し、メモリの使用量が増える可能性があり、表示される可能性があります。結果の数がページサイズよりも大きい場合、ページ付けが厄介になります。

例えば:

def index
  @q = Person.ransack(params[:q])
  @people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
end

 

PG::UndefinedFunction: ERROR: could not identify an equality operator for type json

distinct :true使用中に上記のエラーが発生した場合は、Ransackが選択しているカラムの1つがjsonカラムであることを意味します。 PostgreSQLは、JSONタイプのための比較演算子を提供していません。これを回避することは可能ですが、実際にはPostgreSQLのドキュメントで推奨されているように、それらをjsonbに変換する方がはるかに優れています。

承認(ホワイトリスト/ブラックリスト)

ホワイトリスト方式とは、電子メールやWebサイトのフィルタリングを実施する方法の一つ。安全が確認されている対象のリストを作り、それ以外を排除する方式を取るります。危険な対象をほぼ完全に遮断できる反面、リストの中身が恣意的で、安全な対象の一部に限られてしまい、利用者の利便性を失うという欠点を持っています。Railsに置ける「onlyオプション」がホワイトリスト方式に該当します。
ブラックリスト方式とは、電子メールやウェブサイトのフィルタリングを実施する方法の一つ。スパムメールを大量に送信した者や有害ウェブサイトのブラックリストを作成し、リストアップされたアドレスを通信事業者がブロックします。Railsにおける「exceptオプション」がブラックリスト方式に該当します。

デフォルトでは、検索と並べ替えはモデルのどのカラムでも許可されており、クラスのメソッド/スコープはホワイトリストに登録されていません。

RansackはActiveRecord::Baseに4つのメソッドを追加します。これらのメソッドは、モデルのクラスメソッドとして再定義して、選択的な承認を適用できます。ransackable_attributes、ransackable_associations、ransackable_scopes、ransortable_attributesです。

これらの4つのメソッドがRansackでどのように実装されているかを次に示します。

  # `ransackable_attributes` by default returns all column names
  # and any defined ransackers as an array of strings.
  # For overriding with a whitelist array of strings.
  #
  def ransackable_attributes(auth_object = nil)
    column_names + _ransackers.keys
  end

  # `ransackable_associations` by default returns the names
  # of all associations as an array of strings.
  # For overriding with a whitelist array of strings.
  #
  def ransackable_associations(auth_object = nil)
    reflect_on_all_associations.map { |a| a.name.to_s }
  end

  # `ransortable_attributes` by default returns the names
  # of all attributes available for sorting as an array of strings.
  # For overriding with a whitelist array of strings.
  #
  def ransortable_attributes(auth_object = nil)
    ransackable_attributes(auth_object)
  end

  # `ransackable_scopes` by default returns an empty array
  # i.e. no class methods/scopes are authorized.
  # For overriding with a whitelist array of *symbols*.
  #
  def ransackable_scopes(auth_object = nil)
    []
  end

 

これらのメソッドから返されない値は、Ransackによって無視されます。つまり、許可されません。

4つのメソッドはすべて、単一のオプションパラメータauth_objectを受け取ることができます。モデルでsearchまたはransackメソッドを呼び出すときに、独自のオーバーライドされたメソッドで使用できるオプションハッシュのauth_objectキーの値を指定できます。

これは、Ernie Millerによるこのブログ投稿を基に、これらすべてをまとめた例です。 Articleモデルで、次のransackable_attributesクラスメソッド(できればprivate)を追加します。

class Article < ActiveRecord::Base
  def self.ransackable_attributes(auth_object = nil)
    if auth_object == :admin
      # whitelist all attributes for admin
      super
    else
      # whitelist only the title and body attributes for other users
      super & %w(title body)
    end
  end

  private_class_method :ransackable_attributes
end

 

これがarticles_controllerのサンプルコードです。

class ArticlesController < ApplicationController
  def index
    @q = Article.ransack(params[:q], auth_object: set_ransack_auth_object)
    @articles = @q.result
  end

  private

  def set_ransack_auth_object
    current_user.admin? ? :admin : nil
  end
end

 

Railsコンソールで試してみる:

> Article
=> Article(id: integer, person_id: integer, title: string, body: text)

> Article.ransackable_attributes
=> ["title", "body"]

> Article.ransackable_attributes(:admin)
=> ["id", "person_id", "title", "body"]

> Article.ransack(id_eq: 1).result.to_sql
=> SELECT "articles".* FROM "articles"  # Note that search param was ignored!

> Article.ransack({ id_eq: 1 }, { auth_object: nil }).result.to_sql
=> SELECT "articles".* FROM "articles"  # Search param still ignored!

> Article.ransack({ id_eq: 1 }, { auth_object: :admin }).result.to_sql
=> SELECT "articles".* FROM "articles"  WHERE "articles"."id" = 1

 

これで完了です!これで、Ransackのさまざまな要素をホワイトリスト/ブラックリストに登録する方法がわかりました。

スコープ/クラスメソッドの使用

前のセクションから続けて、スコープで検索するにはモデルクラスでransackable_scopesのホワイトリストを定義する必要があります。ホワイトリストはシンボルの配列である必要があります。デフォルトでは、すべてのクラスメソッド(スコープなど)は無視されます。スコープは真の値を照合するため、またはスコープが値を受け入れる場合は指定された値に適用されます。

class Employee < ActiveRecord::Base
  scope :activated, ->(boolean = true) { where(active: boolean) }
  scope :salary_gt, ->(amount) { where('salary > ?', amount) }

  # Scopes are just syntactical sugar for class methods, which may also be used:

  def self.hired_since(date)
    where('start_date >= ?', date)
  end

  def self.ransackable_scopes(auth_object = nil)
    if auth_object.try(:admin?)
      # allow admin users access to all three methods
      %i(activated hired_since salary_gt)
    else
      # allow other users to search on `activated` and `hired_since` only
      %i(activated hired_since)
    end
  end
end

Employee.ransack({ activated: true, hired_since: '2013-01-01' })

Employee.ransack({ salary_gt: 100_000 }, { auth_object: current_user })

 

Rails3および4では、真の値がurl paramsまたは文字列に変換するその他のメカニズムを介して渡される場合、真の値は配列でラップしない限り(つまり、アクティブ化されない限り)、Ransackableスコープに渡されない場合があります。 [‘true’])。 Ransackは、「true」をboolean値に変更します。これは現在、Rails5で解決されています😃

ただし、おそらくuser_id: 1があり、Ransackが1をboolean値に変換したくない場合があります。 (ブール値にサニタイズされた値はconstants.rbにあります)。これをグローバルにオフにし、型変換を自分で処理するには、config/initializers/ransack.rbなどの初期化ファイルでsanitize_custom_scope_booleansをfalseに設定します。

Ransack.configure do |c|
  c.sanitize_custom_scope_booleans = false
end

 

スコープごとにこれをオフにするために、RansackはActiveRecord :: Baseに次のメソッドを追加します。このメソッドを再定義して、サニタイズを選択的にオーバーライドできます。

ransackable_scopes_skip_sanitize_args

 

この動作をバイパスするスコープをransackable_scopes_skip_sanitize_argsに追加します。

def self.ransackable_scopes_skip_sanitize_args
  [:scope_to_skip_sanitize_args]
end

 

スコープはRansackに最近追加されたものであり、現在いくつかの注意点があります。まず、子の関連付けを含むスコープは子モデルではなく、親テーブルモデルで定義する必要があります。第2に配列を引数として持つスコープは、機能するために配列でラップする必要があるため(この問題を参照)まだ簡単には使用できません。これは、Ransackフォームヘルパーと互換性がありません。このユースケースでは、可能であれば代わりにランサッカーを使用する方がよい場合があります。ソリューションとテストを含むプルリクエストは大歓迎です!

ANDではなくORでクエリをグループ化する

デフォルトのANDグループ化は、クエリハッシュにm: ‘or’を追加することでORに変更できます。

次のように、インデックスアクションのparams [:q]をparams [:q].try(:merge、m: ‘or’)に変更することで、コントローラーコードで簡単に試すことができます。

def index
  @q = Artist.ransack(params[:q].try(:merge, m: 'or'))
  @artists = @q.result
end

 

通常、ユーザーがANDクエリとORクエリのグループ化を切り替えられるようにする場合は、mがURL paramsハッシュに含まれるように検索フォームを設定しますが、ここでは、すばやく試すためにmを手動で割り当てました。

または、Railsコンソールで試してみてください。

artists = Artist.ransack(name_cont: 'foo', style_cont: 'bar', m: 'or')
=> Ransack::Search<class: Artist, base: Grouping <conditions: [
  Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
  Condition <attributes: ["style"], predicate: cont, values: ["bar"]>
  ], combinator: or>>

artists.result.to_sql
=> "SELECT \"artists\".* FROM \"artists\"
    WHERE ((\"artists\".\"name\" ILIKE '%foo%'
    OR \"artists\".\"style\" ILIKE '%bar%'))"

 

コンビネータはデフォルトのandの代わりにorになり、SQLクエリはWHERE … ANDの代わりにWHERE … ORになります。

これはアソシエーションでも機能します。多くのメンバーシップを持ち、メンバーシップを通じて多くのミュージシャンがいるアーティストモデルを想像してみてください。

artists = Artist.ransack(name_cont: 'foo', musicians_email_cont: 'bar', m: 'or')
=> Ransack::Search<class: Artist, base: Grouping <conditions: [
  Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
  Condition <attributes: ["musicians_email"], predicate: cont, values: ["bar"]>
  ], combinator: or>>

artists.result.to_sql
=> "SELECT \"artists\".* FROM \"artists\"
    LEFT OUTER JOIN \"memberships\"
      ON \"memberships\".\"artist_id\" = \"artists\".\"id\"
    LEFT OUTER JOIN \"musicians\"
      ON \"musicians\".\"id\" = \"memberships\".\"musician_id\"
    WHERE ((\"artists\".\"name\" ILIKE '%foo%'
    OR \"musicians\".\"email\" ILIKE '%bar%'))"

SimpleFormの使用

RansackフォームビルダーとSimpleFormフォームビルダーを組み合わせたい場合は、Railsが起動する前にRANSACK_FORM_BUILDER環境変数を設定します。以下に示すように、config/application.rbの前に「rails/all」が必要です(そして、Gemfileにgem「simple_form」を追加します)。

require File.expand_path('../boot', __FILE__)
ENV['RANSACK_FORM_BUILDER'] = '::SimpleForm::FormBuilder'
require 'rails/all'

I18n

Ransack翻訳ファイルはRansack::Localeで入手できます。また、http://www.localeapp.com/projects/2999で入手できるRansackの多くの翻訳の1つに興味があるかもしれません。

フォームの述語と属性の翻訳は、次のように指定できます(その他の例については、Ransack :: Localeの翻訳ファイルを参照してください)。

en:
  ransack:
    asc: ascending
    desc: descending
    predicates:
      cont: contains
      not_cont: not contains
      start: starts with
      end: ends with
      gt: greater than
      lt: less than
    models:
      person: Passanger
    attributes:
      person:
        name: Full Name
      article:
        title: Article Title
        body: Main Content

 

属性名は、グローバルに、またはactiverecordの下で変更することもできます。

en:
  attributes:
    model_name:
      model_field1: field name1
      model_field2: field name2
  activerecord:
    attributes:
      namespace/article:
        title: AR Namespaced Title
      namespace_article:
        title: Old Ransack Namespaced Title

Mongoid

Mongoidのサポートは、ransack-mongoidの独自のgemに移動されました。 RansackはActiveRecordと同じようにMongoidで動作しますが、Mongoidでは関連付けが現在サポートされていない点が異なります。デモのソースコードはここにあります。ランサック検索で呼び出された結果メソッドは、Mongoid::Criteriaオブジェクトを返します。

  @q = Person.ransack(params[:q])
  @people = @q.result # => Mongoid::Criteria

  # or you can add more Mongoid queries
  @people = @q.result.active.order_by(updated_at: -1).limit(10)

 

注: Ransackは現在、Active RecordまたはMongoidのいずれかで動作しますが、同じアプリケーションで両方を動作させることはできません。両方が存在する場合、Ransackはデフォルトでアクティブレコードのみになります。ロジックは、オーバーライドする必要がある場合に備えて、Ransack::Adapters#instantiate_object_mapperに含まれています。

Semantic Versioning

Ransackは、x.y.zの形式でセマンティックバージョニングを実行しようとします。ここで、

xはメジャーバージョン(下位互換性のない新機能)を表します。

yはマイナーバージョン(下位互換性のある新機能)を表します。

zはパッチ(バグ修正)を表します。

言い換えると、Major.Minor.Patchです。

コントリビューションズ

プロジェクトをサポートするには:

  • OpenCollectiveを介したサポートを検討してください
  • アプリでRansackを使用し、壊れているものや不足しているものがあればお知らせください。問題を実証するための失敗した仕様は素晴らしいです。テストに合格したプルリクエストはさらに優れています!
  • 問題またはプルリクエストを提出する前に、必ず寄稿ガイドを読んでそれに従ってください。
  • バグレポート、プルリクエスト、またはドキュメントの改善に直接関係のない質問やディスカッションについては、StackOverflowまたは他のサイトを使用してください。
  • Ransackがあなたに役立ったなら、Twitter、Facebook、その他の場所でその言葉を広めてください。プロジェクトを使用している人が多ければ多いほど、バグをすばやく見つけて修正できます。

 

 

今回は、Railsで検索機能を簡単に実装できるgem「Ransack」のREADMEを翻訳したので紹介しました。中々難しい箇所もありますが、何となくの理解で読み進めていただけたらと思います。