How to Build Your Own Filters with Ransack

ActiveAdmin has some pretty awesome built-in filtering functionality -- and it looks nice to boot!

In a previous post, I covered how these filters work under the covers -- using a sweet gem called Ransack. In this post, I'd like to talk about how to build your own filtering interface similar to how ActiveAdmin does.

Dat Sidebar

So that ActiveAdmin filtering sidebar is pretty tight, but what really is it? It always seemed a bit magical to me. Turns out, it's just a form! In normal Rails projects, we're usually creating forms and pointing them at instances of a model:

form_for @user do |f|

With ActiveAdmin's filtering sidebar, the form points to a Ransack search object. If we had a model User, we'd get a Ransack search object like this:
=> Ransack::Search<class: User, base: Grouping <combinator: and>>

Knowing this, we can start to build our own filtering form to turn into a nice interface component:

search =

form_for search do |f|

Filling out our Filtering Form

Knowing we need to point our form at a Ransack search object is half the battle. Next, we need to add our inputs and define how user input goes into the params.

Form Inputs

When we submit a normal form in Rails, the names and values of our form inputs get inserted into the params on submit:

  'user' => {
    'first_name' => 'bob',
    'last_name' => 'villa'

And in our controllers, we're creating or updating a resource by passing params[:user] to or user.update_attributes.

Now, we know our models, when backed by Ransack, can take query methods and values like so:'first_name_eq' => 'bob', 'last_name_cont' => 'ill')

This should tell us that we want our form inputs named after the Ransack query method and we want the values to be the desired query term.

Using that knowledge (and a few other things I'll cover immediately after), here's a completed form:

form_for[:q]), as: :q, url: users_path, method: :get do |f|
  f.label :first_name_eq, 'First Name'
  f.text_field :first_name_eq
  f.label :last_name_cont, 'Last Name'
  f.text_field :last_name_cont
  f.submit 'Filter'

The form object is a Ransack search object that we've fed any existing queries (params[:q]). Why params[:q]? We define the params key with the as: :q option. Next, we define where we want this form submit to take us with url: users_path, method: :get. Typically, we'd already be on the index page along with the form, but in any case, these options will take us to the index page for our User model as a GET request.

If we wanted a Clear Filters button, we could just have a button that links to our users_path and clears out params[:q]:

button_tag type: 'button' do
  link_to 'Clear Filters', users_path

Handling the Form Output

Upon submission of the form, we'd end up with our query params appended to the URL (since our form method was set to GET) as well as in our params:

  'q' => {
    'first_name_eq' => 'bob',
    'last_name_cont' => 'ill'

If we want to scope our records using our query params, here's what our index action might look like:

def index
  @users =[:q]).result

Then, in our index view, our @users would only contain those that have the first name 'Bob' and a last name that contains 'ill'.

Getting Rid of Blank Query Values

Any form field that isn't filled out will submit with an empty string ("") as its value. I like to wrap my search object and query params with the following methods:

def query_params
  if params[:q].present?
    params[:q].delete_if { |query, value| value.blank? }

def search
  @search ||=
helper_method :search

Any time we access query_params, we're ensuring we only get non-blank query/value pairs. We can also access our search object for our form by simply calling search (form_for search ...).

Wrapping Up

ActiveAdmin's filtering UI looks pretty nice out-of-the-box. Now that you know how to build your own basic filtering form, you can style it up however you like! I made a simple demo app you can check out if you want to see a more complete implementation. You can also play around with it on Heroku!

The examples we used were pretty basic in terms of Ransack queries and we didn't do anything fancy like letting the user select the query predicate to use, so if you want more -- I'd encourage you to check out this post, dig into Ransack, or check out how ActiveAdmin does it in detail.

Ryan is a developer in Viget's Falls Church, VA, HQ, where he believes in being a liason for both the technical and non-technical. He builds elegant tools for clients such as Bozzuto and Millitello Capital—as well as internal tools that we use at Viget every day.

More posts by Ryan