Searching and Sorting on Rails with Ransack
The most recent rails project I worked on was a simple marketplace application where users could browse products, add them to their cart, ‘checkout’, and see their order history. We ended up getting it to work pretty well, but the natural next step was making the navigation of the products easier.
Our products class had many attributes, like name, price, and quantity in stock. It would be helpful if the user could interact with the website to sort the data based on what they were interested in. Our method of displaying the products on the index page was just iterating over all the products and populating a table with the relevant information. It works, however, it’s not very functional if you have many different products or want any control over the display order.
Enter Ransack, the gem that enables powerful searching and table sorting. There are a few steps to getting it working — first, require it in your gemfile and run bundle update. After you have it loaded up, the next step is to go into the action of the controller that you are using to generate the instance variable you want to search/sort.
The original code on the products show page was as simple as setting an instance variable equal to an array or all the product objects.
To use Ransack you must use some new methods that are provided when you install the gem. On line 4 we are calling the ransack method on our Product class and passing in params[:q] — this is where the information from the search field will be passed. Ransack will return all of the items that match your search query, however, you will need to call the .result method on this object to return a usable array of objects. Now your products instance variable will change based upon what search term is passed in, and then only the applicable results will be rendered, just as you would any other collection of objects.
The next step is to build a search form that will pass the search query to params[:q] — fortunately, Ransack comes with a really helpful form builder called search_form_for. You can build a simple search functionality by using the following format and just adjusting the code to reflect the name of the attributes you are searching by.
In this example we have called name_cont; name reflects the attribute of the class we are searching by (Product.name), and cont is Ransack shorthand for contains — Ransack calls these predicates. Ransack interfaces with Active Record to run the SQL search that returns all instances of the Product class that have a name which contains the relevant string. There are many more predicates available to help tailor your search functionality.
This provides a robust solution for making simple search fields for single class attributes, however, Ransack also comes with the functionality to create advanced search fields. In the following example I use the more advanced features of the Ransack form builder to create conditional searches. In my example, this is particularly useful if a user wanted to be able to find all items under a specific price. Ransack even allows the searches to be combined, so a user could find all items under a certain price that contain a specific word.
It looks a bit complex, but if you follow their predefined structure if is fairly simple to access all of the properties of your model and have them as searchable attributes. You’ll note in the screenshot below that there are some attributes, like image or created at, that I don’t want the user to be able to search by. Unfortunately, I haven't yet worked out how to limit these options.
Sortable Tables are another great feature of Ransack. By using the sort_link view helper we can make the headings clickable links that will toggle between sorting the selected column in ascending and descending order.
From my experience using Ransack, it is super easy to create simple search forms. When you start generating more complex search fields there are some quirks (need for post instead of get requests, multiple search fields appearing, limiting selectable model attributes) that arise. Fortunately, there is great documentation on this gem and I’m sure these issues can be overcome.
Helpful Resources: