20 June 2009 · About 7 minutes read

Rails: Building Complex Search Filters with ActiveRecord and ez_where

With the release of Rails 3, the plugins and code described below no longer works. Read the updated post on building dynamic complex queries with composed scopes.

The code for this series of articles is also available:

git clone git://github.com/cblunt/blog-complex_search_filters_with_rails.git

This series of posts shows how to use the ez_where plugin to build a complex search filters into a Rails model. Last time we built a User model with a search method that allowed it to perform complex searches on terms and attributes. In this post, you’ll see how to tie everything together using a controller and search form.

Populating the Database

Before we get started, it would be handy to populate our database with a lot of data to see the search controller in action. To create fixture data, I use Ryan Bates’ Populator and Benjamin Curtis’ Faker gems. Once installed, a short rake task is all that’s needed to populate the database with some pseudo-random data. Alternatively, you can manually create some user records, e.g. by using script/console.

Lets add a populator rake task. First, make sure you have the faker and populator gems installed:

```bashsudo gem install faker

sudo gem install populator```

Then add a populator task to your project, in a new file lib/tasks/populate.rake:

```ruby# lib/tasks/populate.rake

namespace :db do

namespace :populate do

desc "Populate the users table with 1000 random users"

task :users => :environment do

  require 'populator'

  require 'faker'

  # Destroy existing data

  User.destroy_all

  # Repopulate

  User.populate(1000) do |user|

    user.first_name = Faker::Name.first_name

    user.last_name = Faker::Name.last_name

    user.email_address = Faker::Internet.email

    user.status = rand(5) + 1

    user.admin = (rand(10) < 5 ? true : false)

  end

end

end

end```

With that in place, run the rake task to populate your database with some users:

bashrake db:populate:users

(For a more thorough tutorial of Populator and Faker, see Ryan’s screencast).

Generating the Controller

With plenty of data to work with, it’s time to start building our users controller. In this example, I’ve only included the index and show actions. For a real-world app, you would want to include the remaining RESTful actions (edit, create, etc.).

bashruby script/generate controller users index show

You also need to add a route to your config/routes.rb file so Rails can access the users controller RESTfully:

```ruby# config/routes.rb

map.resources, :users```

With some routes set up, it’s a good idea to check that everything is connected properly. To do this, we’ll just ask the users controller index action to fetch a list of users. Remember that, without any parameters, our User.search method just wraps the User.find base method:

```ruby# app/controllers/users_controller.rb

def index @users = User.search(:all) end```

We’ll then output these user’s on the index page:

```ruby# app/views/users/index.html.erb

Users

<% @users.each do |user| %> <% end %>
Name Email Address Administrator Status
<%= h [user.first_name, user.last_name].join(' ') %> <%= h user.email_address %> <%= user.admin? ? 'Yes' : 'No' %> <%= user.status %>

```

A quick visit to http://localhost:3000/users will check everything’s hooked up (make sure your server is running; script/server will start the development server). If all has gone well, you will have a list of several hundred user names.

The Search Form

Next, we’ll add a text box so that we can search for users by terms. These terms will be passed on to the User.search method and used to filter users by first or last name, or email address. See part 1 for more information on how this works.

In your index view, add the following form just above the results table:

```ruby# app/views/users/index.html.erb

<% form_tag users_path, :method => :get do %>

<%= text_field_tag “search[terms]”, params[:search][:terms] %>

<%= submit_tag “Search” %>

<% end %>

…```

Notice that we need to make the form submit a GET request; if we used the default POST request, Rails’ RESTful routes would assume we wanted to create a new user. If you now reload the page in your browser, entering some search terms should direct you to a URL similar to:

texthttp://localhost:3000/users?search[terms]=joe&amp;commit=Search

So now all we need to do is pass on those search parameters from our controller to User.search. In your controller, change the index action to:

```ruby# app/controllers/users_controller.rb

def index

params[:search]   = {}

@users = User.search(:all, :filters => params[:search])

end```

Now reload your page, and enter some search terms in the text field. You’ll notice that any terms you enter are matched against the users’ names or email addresses.

The Final Touches

With the general search terms working, it would be useful to narrow down our search according to some attribute filters. To do this, we’ll add a checkbox and dropdown menu that allows us to filter results by role and admin status. In index.html.erb, update your form with two new tags:

```ruby# app/views/users/index.html.erb

<% form_tag users_path, :method => :get do %>

<%= text_field_tag “search[terms]”, params[:search][:terms] %>

Only Admins <%= check_box_tag “search[admin]”, true, params[:search][:admin] %>

Status: <%= select_tag “search[status]”, options_for_select([[”- All -“, nil], [“Viewer”, “1”], [“Member”, “2”], [“Subscriber”, “3”], [“Publisher”, “4”], [“Editor”, “5”]], params[:search][:status]) %>

<%= submit_tag “Search” %>

<% end %>```

Your controller will now automatically pass on the :admin and :status values to User.search. However, if you try searching for administrators by checking the box, you’ll see that no users are returned!

The reason for this is how Rails passes parameters into your controller. The check_box_tag returns a string value depending on its status. Our User.search method expects an :admin filter to be a Boolean value, or nil. So all we need to do is tell Rails to convert the string “true” to a boolean true, or set the :admin filter to nil.

This is easy to solve by adding a single line in your controller to map the value of the checkbox to true or nil:

```ruby# app/controllers/users_controller.rb

def index

params[:search]   = {}

# Ensure that params[:search][:admin] is either true or nil.

params[:search][:admin] = (params[:search][:admin] == “true” ? true : nil)

User.search(:all, :filters => params[:search])

end```

Now when you reload the page, you’ll be able to search for users and drill down your search results using your new form!

Next Steps

I hope you’ve found this mini-tutorial series useful and interesting. It’s been a great learning experience for me in writing tutorials, and forcing me to refine my ruby coding. I’ve also found the search functionality useful in my own projects, and have decided to develop it into a generic ActiveRecord plugin. I’ll continue to post updates about progress on this blog, and on my github account.

In the near future, I’ll post some supplementary information on using the search methods with the will_paginate plugin, and how to AJAXify your seach forms.

In the meantime, if you have any comments or feedback, please let me know in the comments. Thanks!

Chris Blunt
Chris Blunt @cblunt
Chris is the founder of Plymouth Software. As well as code and business, he enjoys being a Dad, swimming, and the fine art of drinking tea.