Here’s a neat trick I saw Ryan Bates do in a Railscasts episode at some point. I honestly can’t find the episode now for reference, but here’s what he did: Using a single controller method, he generated list views of a given model, filtered based on what was passed into the method.
Why would you want to do this? Say you’re writing a blog (sorry to be trite, but it works well for this example). You’ve got three models in your blogging app: Authors, Categories, and Posts. You want to be able to list Posts in the following ways:
|. Listing||. Path|
|Posts by category||/categories/3/posts|
|Posts by author||/authors/27/posts|
I know those paths could be more human and search engine friendly by replacing the IDs with a token of some kind; I’ll talk about ways to do that in a future post. Check out FriendlyID or this Railscasts episode if you’re in a hurry.
In order for this to work, the first thing you’ll need to do is make sure your model relationships are in place. So an Author has many Posts, a Category has many Posts (for the sake of simplicity we’ll just assign a Post to a single Category, though this technique will apply to a more complex relationship), and a Post belongs to an Author and belongs to a Category.
Here’s the first part of the trick—you’re not limited to nesting a route under a single parent route. Rather, you can nest it under as many parents as you need to. You can also leave it un-nested. So what does this mean? We can create the following routes (in Rails 2.3.x style):
which gives you the following routes:
|. Listing||. Path|
|Posts by category||/categories/:category_id/posts|
|Posts by author||/authors/:author_id/posts|
:author_id are now
param values that you can access in your controller. Let’s look at that now.
The second part of the trick happens in
posts_controller.rb. What you need to do here is check for which
param (if any) was passed into the
index method, since that’s what we’ll use to generate a list of posts. Here’s what happens:
group_id is passed (like
/categories/3/posts) the controller returns a list of that category’s posts to the
index view. If
author_id is passed (via
/authors/27/posts) we get that author’s posts. If neither is passed (
/posts) we get a list of all posts.
I’m working on a project at work that involves tagging items and then sharing those items in one or more groups (similar to categories in the example I made up above). Using the method I just outlined, I can display a list of tags that are used on items shared with a given group, or a list of tags used by an individual user, or a list of all the tags in my system.
If you liked my series on practical advice for adding reliable tests to your Rails apps, check out the expanded ebook version. Lots of additional, exclusive content and a complete sample Rails application.
Ruby on Rails news and tips, and other ideas and surprises from Aaron at Everyday Rails. Delivered to your inbox on no particular set schedule.