Key Takeaways
- Ransack is a Ruby gem that simplifies the creation of complex search functionality in Rails applications. It provides powerful features like sorting or conditional search without the need for extensive code writing.
- Ransack uses helper methods such as ‘search_form_for’ to create search forms and predicates to determine what information to match. Predicates like ‘:title_cont’ instruct Ransack to search for a title that contains a specific value.
- Advanced search functionality, such as searching for movie price ranges by providing a minimum and maximum value, can be implemented with Ransack. The symbols ‘:price_gteq’ and ‘:price_lteq’ represent ‘price greater than or equal to’ and ‘price less than or equal to’ respectively.
- Ransack also offers form builders that provide links to sort columns in ascending or descending order, and the ability to add conditional search fields. This allows for a select box with all model attributes and another with all predicates, with a final input for the value to use in the search.
In this tutorial we will explore how to add complex search functionality into your Rails application. This task will be made easier by the awesome Ransack Ruby gem. Ransack provides excellent helpers and builders for handling advanced searches on your models. It has some really powerful features available out of the box without writing a lot of code, such as sorting or conditional search.
We will use the same application used in the Braintree series . Please refer to that post to setup and style the basic application. Here is a fork of the repository we will use for our application Ransack – MovieStore.
Goal
Here is a list of features we are going to implement:
- Search movies by title
- Search movies by price ranges
- Sort by title, price, release year, ascending or descending
- Search movies by any of its (model) attributes, with options like equal to, greater than, less than, etc.
- Sort by any model attribute
You can view a working demo of this app deployed on Heroku
Exploring Ransack provided demo
ActiveRecordHackery has their own demo application using the Ransack gem here. It provides two modes for searching: basic and advanced. In basic mode, you can only search by first name, last name, email, or post title. In advanced mode, you have a lot of options to customize the search. Try them out to find the power that Ransack provides in complex searching.
Tools
- Ruby
(2.1.0)
– Programming language - Rails
(4.1.1)
– Back-end framework - Foundation 5 – Front-end CSS framework
- Ransack – Ruby gem
- Devise – User authentication
Step 1: Adding Ransack
The first step is to add the Ransack gem to your Gemfile:
gem 'ransack'
Make sure to run bundle install
.
Step 2: Add a Search Object
In the most common use case, the Ransack-provided search
method is called on the model, passing in the search parameters:
@search = Movie.search(params[:q])
The result can be stored in the @movies
instance variable:
@movies = @search.result
The entire index action looks like:
def index
@search = Movie.search(params[:q])
@movies = @search.result
end
Step 3: Add a Search Form
Let’s create a form to handle the search. The form just needs a text field (for the movie title) and a submit button. The form can leave just under the site header.
Add a yield
statement in the header section in the application layout. The content for this yield
is created in the index view.
Open up layouts/application.html.erb file and add:
<%= yield(:search) %>
in the header section. Then, in the index page, add the simple search form as follows:
<% content_for :search do %>
<div class="large-8 small-9 columns">
<%= search_form_for @search do |f| %>
<%= f.text_field :title_cont, class: "radius-left expand", placeholder: "Movie title" %>
</div>
<div class="large-4 small-3 columns">
<%= f.submit "Search", class: "radius-right button" %>
</div>
<% end %>
<% end %>
As you can see, we used Ransack’s helper method search_form_for
to create our search form and passed the search
object created previously. It contains a text_field
with a symbol :title_cont
, which tells Ransack to search for a title that contains some value. The cont here is called a predicate and it is the way Ransack determines what information to match.
As a simple refactoring I moved this content for to a separate partial (_title_search_box.html.erb) in order for it to be reusable. Add a line to the index page to render this partial:
<%= render "title_search_box" %>
Note: Ransack also has a helper for labels, but we didn’t need it in our form.
Step 4 – 1: Add Price Range
In the previous step, we created simple searching functionality. Let’s try something more advanced, like searching for movie price ranges by providing a minimum and maximum value.
This is what we are going to achieve:
We’ll start with search box styling, borrowing some CSS classes from the MovieStore application. The form-container and glassy-bg classes (found in layout.css.scss and helpers.scss, respectively) add a glassy background to the search box. I created a new div with class column
to contain the advanced search box. The box is hidden, initially.
<div class="column">
<div class="advanced-search hide form-container glassy-bg columns">
<a class="close-advanced-search fi-x"></a>
</div>
</div>
Add a button which, when clicked, opens up the advanced search box, as follows:
<div class="column">
<h5>
<a class="show-advanced-search">Advanced Search <span class="fi-plus"/></a>
</h5>
</div>
Now, in movies.js.coffee, add the click
event for this link, showing our advanced search box:
$ ->
$('.show-advanced-search').click ->
$('.advanced-search').show() // which removes the hide class
$(this).hide()
Handle close advanced search box click
event, as well:
$('.close-advanced-search').click ->
$('.advanced-search').hide()
$('.show-advanced-search').show()
Step 4 – 2: Adding Price Range
Time to add the price range to the search form. We will use the same search_form_for
method to create our form with the same @search
instance variable.
In the below code, the symbols :price_gteq
and price_lteq
represent for price greater than or equal to and price less than or equal to, respectively. View a list of the available predicates here
<h4>Advanced Search</h4>
<%= search_form_for @search do |f| %>
<div class="large-5 small-4 columns">
<%= f.label :price_gteq, class: "movie-label" %>
<%= f.text_field :price_gteq, class: "radius", placeholder: "Minumum Price" %>
</div>
<h6 class="large-2 small-4 columns center"><span>And</span></h6>
<div class="large-5 small-4 columns">
<%= f.label :price_lteq, class: "movie-label" %>
<%= f.text_field :price_lteq, class: "radius", placeholder: "Maximum Price" %>
</div>
<div class="column">
<%= f.submit "Search", class: " radius button" %>
</div>
<% end %>
Step 5: Add Sorting
Ransack has a form builder that provides links to sort columns in ascending or descending order. This builder is sort_link
. It is very simple to use, just pass the search object and the column name (attribute) you want to sort and a placeholder for it.
I’ve overridden three Foundation labels in the foundation_and_overrides.scss file.
<h4 class="column">Featured Movies</h4>
<div class="row right padm">
<div class="column">
<div class="filter-label red">
<%= sort_link @search, :title, "Title" %>
</div>
<div class="filter-label dark-golden-rod">
<%= sort_link @search, :price, "Price" %>
</div>
<div class="filter-label dark-slate-gray">
<%= sort_link @search, :release_year, "Release Year" %>
</div>
</div>
</div>
Step 6: Add Conditional Search
In this step, let’s explore how to add conditional search fields. Make a select box with all of the movie attributes and another with all of the predicates. The final input will be a text field for the value to use in the search. Ransack has some form builders for this scenario, as shown in the below code.
Just for fun, I’ve added some sorting functionality, but in a form of selects. One dropdown is for model attributes, and the other is the order (ascending, or descending):
<h4>Advanced Search</h4>
<%= search_form_for @search do |f| %>
<%= f.condition_fields do |c| %>
<div class="large-4 small-4 columns">
<%= c.attribute_fields do |a| %>
<%= a.attribute_select nil, class: "radius" %>
<% end %>
</div>
<div class="large-4 small-4 columns">
<%= c.predicate_select Hash.new, class: "radius" %>
</div>
<div class="large-4 small-4 columns">
<%= c.value_fields do |v| %>
<%= v.text_field :value, class: "radius" %>
<% end %>
</div>
<% end %>
<h5>Sort</h5>
<div class="column">
<%= f.sort_fields do |s| %>
<%= s.sort_select Hash.new, class: "large-5 small-4 columns mrs radius" %>
<% end %>
</div>
<%= f.submit "Search", class: "radius button" %>
<% end %>
Step 7: Refactoring Searching
It’s a good idea to move our code to a partial called condition_fields:
_condition_fields.html.erb
<div class="field">
<div class="large-4 small-4 columns">
<%= f.attribute_fields do |a| %>
<%= a.attribute_select nil, class: "radius" %>
<% end %>
</div>
<div class="large-4 small-4 columns">
<%= f.predicate_select Hash.new, class: "radius" %>
</div>
<div class="large-4 small-4 columns">
<%= f.value_fields do |v| %>
<%= v.text_field :value, class: "radius" %>
<% end %>
<%= link_to "remove", "#", class: "remove_fields" %>
</div>
</div>
Step 8: Link to Add Criteria
Let’s add a link in the advanced search box to add conditions. In index.html.erb, add a link to add the criteria, like so:
<%= f.condition_fields do |c| %>
<%= render "condition_fields", f: c %>
<% end %>
<p><%= link_to_add_fields "Add Conditions", f, :condition %></p>
First, create the link_to_add_fields
helper method in the application helper:
def link_to_add_fields(name, f, type)
new_object = f.object.send "build_#{type}"
id = "new_#{type}"
fields = f.send("#{type}_fields", new_object, child_index: id) do |builder|
render(type.to_s + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
In movies.js.coffee, handle the click
event for the add_fields
class:
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
Step 9: Link to Remove Criteria
It is sensible to also add a link to remove conditions. Add it in the condition_fields partial, accompanying every condition:
<div class="large-4 small-4 columns">
<%= f.value_fields do |v| %>
<%= v.text_field :value, class: "radius" %>
<% end %>
<%= link_to "", "#", class: "remove_fields fi-x" %>
</div>
Back in movies.js.coffee, the necessary click
event handler:
$('form').on 'click', '.remove_fields', (event) ->
$(this).closest('.field').remove()
event.preventDefault()
Step 10: POST the Request
If we add too many conditions, the number of params may reach the limit allowed by GET requests. This can be avoided by using POST instead.
In the config/routes.rb file add:
resources :movies, only: [:show, :index] do
match :search, to: 'movies#index', via: :post, on: :collection
end
Then, in index.html.erb, change the search form to use the new search path, like so:
<%= search_form_for @search, url: search_movies_path, method: :post do |f| %>
Summary
In this tutorial, we used the same online movie store application used in “Build an Online Store with Rails” to add complex searching functionality. By using the Ransack gem, the movie application now has a nice interface for both simple and complex searching.
Frequently Asked Questions (FAQs) about Advanced Search with Ransack
What is Ransack and how does it work in Ruby on Rails?
Ransack is a powerful gem in Ruby on Rails that simplifies the creation of search forms. It works by creating a form that builds a search query to display the desired results. Ransack provides a simple and intuitive way to create complex searches without the need to write lengthy SQL queries. It allows users to search models and display the results in a user-friendly manner.
How do I install and set up Ransack in my Rails application?
To install Ransack, you need to add the line gem 'ransack'
to your Gemfile and then run bundle install
from your terminal. After installation, you can use Ransack in your controllers and views. In the controller, you can use the ransack
method on your model to create a search object. In the view, you can use the search_form_for
helper method to create a search form.
How can I use Ransack for advanced search functionality?
Ransack allows you to create advanced search forms with a variety of options. You can search for records that match all or any of your criteria, and you can also search for records that do not match certain criteria. Ransack also supports sorting of results, which can be done by clicking on the column headers in the results table.
Can I use Ransack with pagination gems like Kaminari or Will_paginate?
Yes, Ransack works well with pagination gems like Kaminari or Will_paginate. After creating a search object with Ransack, you can use the result
method to get the search results and then paginate them using the pagination gem’s methods.
How can I customize the search form created by Ransack?
Ransack provides a number of helper methods that you can use to customize your search form. For example, you can use the sort_link
helper to create sortable links for your columns, and you can use the search_field
helper to create search fields for your attributes.
How can I use Ransack to search associated models?
Ransack allows you to search associated models by using the associations
method in your search form. You can specify the association and the attribute you want to search in the form, and Ransack will handle the rest.
Can I use Ransack to search for records in a specific date range?
Yes, Ransack supports date range searches. You can use the gteq
and lteq
predicates in your search form to search for records that fall within a specific date range.
How can I handle complex searches with Ransack?
For complex searches, Ransack provides the ransacker
method, which allows you to define your own search methods. You can use this method to create custom searches that are not supported by the default Ransack methods.
Can I use Ransack with non-ActiveRecord models?
Ransack is designed to work with ActiveRecord models, but it can also be used with non-ActiveRecord models with some additional configuration. You will need to include the Ransack::Adapters::Object
module in your model and define the searchable attributes manually.
How can I troubleshoot issues with Ransack?
If you encounter issues with Ransack, you can check the Ransack GitHub page for solutions to common problems. You can also post your issue there to get help from the community. Additionally, you can check the Rails console for any error messages that might give you a clue about what’s going wrong.
Islam is a freelance web developer with experience spanning the full stack of application development. He is a co-founder of Whitespace which is a web development agency. Besides for that he spends his time working on open source projects that he find intriguing or writing tutorials. He was an ex-Google Summer of Code student in 2012 and a mentor for 2 projects in 2013 for KDE. You can find him on Twitter @wazery_ or check his Linkedin profile linkedin.com/in/wazery.