Wednesday 24 July 2013

The Rails Way to Search Filtering

It's been a while since my last post. Numerous changes took place in my professional life since then. I decided to take a break from my career and focus my energy to learning new technologies outside my area of expertise. One biggie is Ruby on Rails 3.

To test my RoR as well as my UI skills, I created evplore.com: a website for sharing, discovering and creating events. A requirement for evplore is to allow users to filter events by country, locality, tag, or date as shown below:

Before delving into the nitty-gritty of implementing search filtering, let's have a glance at the EventsController:

The controller does what you might expect: it invokes the Event model's find method to retrieve the events that match the user's search criteria.

Here's a dirty way of implementing the find method inside the Event model:

Any programmer worth his salt will note that this solution is tough to maintain. The find method has a branch for every possible way the user can apply search filters (in all 2^n branches). Having more than a few filters causes an explosion in the number of elsif branches.

The Rails way to solving this problem is to use query chaining along with the scoped method:

The Event model now avoids issuing a different query for each way search filters are applied. Instead, every query corresponding to a filter is put in its own class method. All methods do the same thing more or less. For example, in_country takes as a parameter the country submitted by the user. If the parameter is nil or blank, this means the user did not submit a country filter so the method should skip the query and return the relation as is: this is what the scoped method is for. Otherwise, the filter query is applied on the current relation and the new relation is returned.

What's left is for the EventsController to chain together the query methods so that the result of each query is passed on to the next query method for further filtering:

The complete code for this demo is available on GitHub.