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.