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 class="lead">Narrow results:</p>
<div class="form-group">
<%= f.label :title_cont, "Title containing" %>
<%= f.text_field :title_cont, :class => "form-control", :placeholder => "Enter a few characters" %>
</div>
<div class="form-group">
<%= f.label :year_gteq, "Released after" %>
<%= f.text_field :year_gteq, :class => "form-control", :placeholder => "Year greater than or equal to" %>
</div>
<div class="form-group">
<%= f.label :year_lteq, "Released before" %>
<%= f.text_field :year_lteq, :class => "form-control", :placeholder => "Year less than or equal to" %>
</div>
<%= f.submit :class => "btn btn-primary btn-block" %>
<a href="/movies" class="btn btn-default btn-block">Clear filters</a>
<% end %>
From the above example, you need to customize the following:
- Crucially, the names of the inputs (in the example,
: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. - The labels (in the example,
"Title containing"
,"Released after"
, and"Released before"
). - The placeholder text.
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 class="form-group">
<%= f.label :director_name_cont %>
<%= f.text_field :director_name_cont, :class => "form-control" %>
</div>
<div class="form-group">
<%= f.label :actors_name_or_actors_bio_cont %>
<%= f.text_field :actors_name_or_actors_bio_cont, :class => "form-control" %>
</div>
<div class="form-group">
<%= 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 }, :class => "form-control" %>
</div>
A few things to note:
-
The new input names start with the association, then the column (
:director_name_...
,:actors_name_...
). Pluralization matters. -
You can chain multiple columns together with the same criterion by using
or
(:actors_name_or_actors_bio_cont
). -
If you prefer a dropdown over a text field, you can do it as shown above for actors.
-
If you prefer checkboxes, you can use code like this:
<div class="form-group"> <% 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>