Each post on my site describes an online resource. Each post consists of a title, a URL, a brief description, and a list of categories under which the resource falls. Other users may comment and vote on posts.
The categories are created by the users, so they can get numerous. To make navigating through categories easier, I implemented a search feature. A user may have to run many searches before finding the category she wants, and I didn't want to make her click a submit button for every search. Instead, I wanted a "live search", which is run automatically every time the text in the corresponding input element is modified.
I liked building this feature in Rails, because the implementation involves all three parts of the Model-View-Controller pattern, as well as some client-side processing. For the most part, I followed the implementation suggested by Ryan Bates in his Railcasts series. In this post, I will describe the model side of that implementation and comment on a couple of details of ActiveRecord queries.
Model:
On the model side, we need a method on the Category class that will do the actual searching. Here it is. I want to comment on two aspects of this code. The first, not immediately apparent (to me), is that it doesn't actually return an array of Category objects. ActiveRecord query methods, like where and all, generate SQL queries but don't run them directly. Instead, they return an ActiveRecord::Relation object. It is only when a kicker method like each is called on the ActiveRecord::Relation that the records are loaded from the database into memory. This approach, called lazy loading, conserves memory by not loading records until the instant they are needed. It also improves performance by pushing tasks down to the database, which is faster than the Ruby interpreter at things like sorting and summing. For example, generates the following SQL query This is faster than pre-loading all the records and then sorting them in memory.
If we had wanted the opposite of lazy loading, eager loading, we would have called load on the ActiveRecord::Relation returned by where and all. The records would then have been retrieved and stored in an array instance variable on the ActiveRecord::Relation.
The second point I wanted to comment on is illustrated on line 11. That code would also have worked if we had constructed a SQL statement directly and passed it to where However, this is a bad idea, because we would be passing unsanitized user input to the database, leaving it vulnerable to a cross-script injection attack.
In the following posts, I will tackle the controller, view, and client-side parts of my live search implementation.