Feature Request 1: Rails Phonebook-style Navigation
Welcome to the first installment in my new weekly series! In these "Feature Requests," we're going to talk about how we can get things done. While it's important to learn new concepts, they will always be just concepts if you never apply them. This series will continually pose a client's request in an attempt to understand how we can move from concept to application. The topics may shift. Today's focus is on Ruby on Rails, but that's not to say we won't be writing JavaScript or CSS in the future.
The Request
I've got a site that manages some flowers that I sell. I've got over 200 flowers available, and the list has gotten huge! Can you find some way to separate the list by first letter, like in a phonebook?
Let's build out a Rails project that will accomplish this task.
Setting Up The Project
For this project, we'll use Rails 3.0.7. First, let's create a new Rails application. Also, we'll change directories and bundle our gems.
$ rails new flowers $ cd flowers $ bundle install
To get going quickly, we are going to use scaffolding to generate the FlowersController and Flower classes. Each flower will have a name attribute so that we can call them something.
$ rails g scaffold Flowers name:string $ rake db:migrate
Finally, we need to populate our database with some flower names if we're going to mimic the functionality our client's application currently has. I've already prepared a Rake task with over 200 flower names that will create some records. Let's pull that file down into our project.
$ curl https://raw.github.com/gist/1250132/a263d63adfe870a4a11a3e9909d4444d04c3fb1e/add_flowers.rake -o lib/tasks/add_flowers.rake $ rake add_flowers
Now that we have some records, we can view our flowers index page and see the giant list our client was talking about. Start the development server and navigate to:
Dag yo, that's a lot of flowers.
Selecting Flowers By First Letter
ActiveRecord has a huge amount of amazing methods. In Rails 3, these methods got even cleaner to use. However, here's our first pain point: the scaffolding currently uses the all method to ask for the entire list of flowers. We want to pare that down to just a small number of flowers, so we're going to only select flowers that share their first letter.
Let's write a scope on our Flower class that will let us perform the search we want. Using good practices, we will write the test first. Crack open the test/unit/flower_test.rb file that was generated for us. In here, you'll find an example test. Remove it, and let's write the test.
First, we create a flower named "Test". Then, we do a search for this flower, since it starts with the letter "T". We expect the returned array to include the flower that we created. When we run the test, it fails. We haven't yet implemented the class method with_first_letter. Now, we can implement that code to make the test pass.
ActiveRecord scopes are a very handy tool. They allow you to build up conditions and SQL clauses that will find only the records you want to find. To write our own scope, we first give it a name for the class method. In our case, the next step is to use a lambda, or anonymous function, to allow our scope to accept an argument. Inside the curly braces, we can run any code we want to. Any conditions, such as where, as well as other scopes that are called inside this block are added to this new scope.
This gives us some freedom. Since we used a LIKE clause, we prepare the statement by interpolating the letter argument first. We append the percent sign to the end of it to signal that we don't care what comes after it. It's like a catch-all wildcard, since we just care that it starts with whatever letter is passed. Last, we add our parameter to a where clause.
Heading back to our test, we can run it and everything checks out okay. Now, we can start implementing this in our controller.
Removing The Evil "ALL" Method
Let's head over to our flowers controller again. Instead of making a call to all, we can use our scope we wrote.
We want this letter we're passing to be variable, so we used a value from the params hash. If we go back to our browser, we can specify the parameter ourselves by using the query string at the end of the route. For example, we can select flowers that start with the letter "C" by navigating to http://localhost:3000/flowers?letter=C.
Query strings to reduce the number of results. Boom!
The last thing we need to finish the navigation are links to each letter. We can use Ruby's ranges to enumerate over the alphabet and produce our links dynamically. Open up app/views/flowers/index.html.erb and add this code just above our table:
Now, we can click through the different letters and see only flowers that start with that letter.
Creating a Custom Route
Now that things are working, we can do a little more to clean up how our routes are working. Right now, we're specifying this query string in our route, and it just looks downright ugly. A more pretty route would be something like /flowers/page/C. To create this route, open up config/routes.rb and add this code:
With this route, we can reuse the index action. Even though there is already a route to our index action, there's nothing stopping us from mapping several different routes to any one action. Our route in place, we need to change which route we're using in our index view.
Now our phonebook-style navigation is complete! Not only does this new route look prettier, it will actually allow Rails to cache the results easily if you've enabled caching.
Did you like this post? If so, you should follow me on Twitter, and I would appreciate an email from you!
Tweet
