How to Build Your Own Filters with Ransack
Ryan Stenberg, Former Developer
Article Category:
Posted on
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| ... end
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:
User.search => 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 = User.search form_for search do |f| ... end
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 User.new
or user.update_attributes
.
Now, we know our models, when backed by Ransack, can take query methods and values like so:
User.search('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 User.search(params[: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' end
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 end
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 = User.search(params[:q]).result end
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? } end end def search @search ||= User.search(query_params) end 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.