The Ransack gem provides us with a powerful, flexible, easy-to-integrate search/filter form.
In your Gemfile, include
gem 'ransack'
and then bundle install
. Restart your rails server
.
First, modify your controller action from:
def index
@movies = Movie.all
end
to:
def index
@q = Movie.ransack(params[:q])
@movies = @q.result
end
Next, add a search form to the view template:
<%= search_form_for @q do |f| %>
<p>Narrow results:</p>
<div>
<%= f.label :title_cont, "Title containing" %>
<%= f.text_field :title_cont, :placeholder => "Enter a few characters" %>
</div>
<div>
<%= f.label :year_gteq, "Released after" %>
<%= f.text_field :year_gteq, :placeholder => "Year greater than or equal to" %>
</div>
<div>
<%= f.label :year_lteq, "Released before" %>
<%= f.text_field :year_lteq, :placeholder => "Year less than or equal to" %>
</div>
<%= f.submit :class => "btn btn-primary btn-block" %>
<a href="/movies">Clear filters</a>
<% end %>
From the above example, you need to customize the following:
:title_cont
, :year_gteq
, and :year_lteq
). The names are very particular: the first part needs to be the name of the column you want to search, and then an underscore, and then a predicate that indicates what kind of search you want. The most common ones are _eq
(exactly equals), _cont
(contains), _gteq
(greater than or equal to), and _lteq
(less than or equal to). Here is a full list of all available predicates. Add fields for however many columns you want to allow users to narrow by."Title containing"
, "Released after"
, and "Released before"
).If you want to allow your users to narrow by attributes of associated objects, change your action to look like this:
def index
@q = Movie.ransack(params[:q])
@movies = @q.result(:distinct => true).includes(:director, :actors)
end
In your app, customize .includes(:director, :actors)
with the relevant associations that you want to include in the query.
In the view, you can now add the additional inputs to the search form:
<div>
<%= f.label :director_name_cont %>
<%= f.text_field :director_name_cont %>
</div>
<div>
<%= f.label :actors_name_or_actors_bio_cont %>
<%= f.text_field :actors_name_or_actors_bio_cont %>
</div>
<div>
<%= f.label :actors_id_eq, "Actor" %>
<%= f.select :actors_id_eq, options_from_collection_for_select(Actor.all, :name, :name, @q.actors_id_eq), { :include_blank => true } %>
</div>
A few things to note:
:director_name_...
, :actors_name_...
). Pluralization matters.or
(:actors_name_or_actors_bio_cont
).<div>
<% Actor.all.each do |actor| %>
<label>
<%= check_box_tag('q[actors_id_eq_any][]', actor.id, (true if @q.actors_id_eq_any.try(:include?, actor.id))) %>
<%= actor.name %>
</label>
<% end %>
</div>
Happy filtering!
For actual intelligent, fuzzy, searching, things get more involved; options include Postgresās built-in search (via a gem like pg_search), Elasticsearch (via a gem like SearchFlip), or Algolia. All of these are relatively advanced techniques that require some re-configuration of the database itself.