folder_for: A Controller DSL (2007-06-02)
Over the last year or so, I’ve spoken at a number of meetings and conferences (including The Rails Edge and RailsConf) about view layer concerns, and on several occasions have given a folder interface example when speaking about controller domain specific languages. Since I’ve received several requests, here’s some information on the example (and the code, of course).
First of all, here’s the source
The Slimy Used Car Salesman App (beta)
For our example, let’s pretend for a moment that we’re developing an application that will serve as a local listing of used cars for sale. After talking with the client, we’ve determined that in makes sense that a folder-style interface to be used on a few pages, most notably when viewing the information for a specific car. Folder interfaces aren’t hard, but they can be tedious—so we’re going to employ a DSL so we can easily create them with a minimum of hassle. We could do this purely in the view layer, but we’d like the flexibility to add custom code for individual tabs in the controller, and don’t want to have to remember a series of helpers that we’ll need in views.
Using this plugin, we can do something like the following:
class CarsController < ActionController::Base
# ...
folder_for :show do
tab "General Information" do
@score = current_user.score_for_car(@car)
end
tab "History"
tab "Photos"
end
# ...
end
What we’re doing here is defining a folder to be displayed for the show action, with 3 tabs. On the first tab, General Information, we’d also like to calculate a score for the car, based on the currently signed-in user’s preferences.
So, what happens now?
We have to define the content of each tab. In the past, we would have one view template to work with:
cars/
show.html.erb
and would have had to manually render the partial for the currently selected tab, based on a parameter, and handled setting the default tab, not to mention the mechanics of switching tabs. Perhaps, if we were exceedingly lazy, we would have just had a big, ugly if, elsif statement in our template as well, conditionally rendering content for tabs. Ick.
How this works now, by convention, is that we break up our templates/partials like:
cars/
show.html.erb
show/
_general_information.html.erb
_history.html.erb
_photos.html.erb
and our show.html.erb might look something like:
<h1><%= @car.title %></h1> <p><%= @car.summary %></p> <%= folder %> <p>Some information below the folder...</p>The folder helper essentially says “put the folder here,” and the content that’s inserted consists of:
- A set of easily-styleable, functional tabs (a ul), with the current tab selected (through a CSS class)
- The content from the appropriate tab partial inserted in a div within the folder
The concerns of the folder system - the fact that it involves the use of params[:tab], the first tab is the default, and links need to be generated to switch from tab to tab - is not something you need to care about; these are mundane details that this level of abstraction allows you to ignore.
Another thing you may have noticed is the fact that @car is available for use—both within the tab definitions in the controller (where, in our example, we set @score) and in views. How is this handled? Using REST conventions (the fact we’re doing this within CarsController), the folder knows that there should be a Car model, and that it should set @car using Car.find(params[:id]). Once again, a mundane detail that remains beneath your notice.
Caveat Emptor
It’s important that I point out a few details on the limitations of this plugin—and very unapologetically, since it’s an example and not a release.
- Cases where a record need not be queried (ie, we don’t need @car), or multiple records are needed (ie, if we were setting up a folder on index) are not handled by this implementation—but could easily if an options hash argument was handled by folder_for. I sacrificed completeness for [some semblance of] clarity.
- There is no support for multiple folders-per-view (though there is support for multiple folders per controller), since I think that’s a fairly stupid interface decision, so adding support for it is stupid, too.
- The code may or may not work with Rails versions prior to support for .html.erb template extensions (ie, the old .rhtml days)
- There’s probably a number of other limitations; feel free to extend as you see fit.
In My Defense
There are likely several of you reading this that are disturbed to see a folder reference within a controller, as this amounts to level type of MVC “separation of concerns” blasphemy in your very strict, very well-worn book of religious morals.
I’m here to tell you it’s okay, and you’ll recover in time.
Let’s keep in mind here that the MVC separation of concerns, while a great rule of thumb - is just that - and is not an ivory tower to be left unassailed in times of dire need. At times, it makes sense to allow abstractions to cross these boundaries for the sake of reducing our own overhead, and in the cause of developing your own app-wide domain specific language—something, that in my book, is the principal sign of a good Rails developer (for what it’s worth).
Malleable Fun (or, the Moral of the Story)
An important thing to remember when using Rails is that it sits on top of Ruby—something that’s so obvious that it’s moronic, but nonetheless something people seem to forget.
What this means is that you have this amazingly dynamic, expressive language at your fingertips, and you’re free to extemporize as you see fit, creating your own mini languages in a flash.
Think something sucks to do every time manually? Don’t. Write code to do it, or write code to write code to do it. You’re language crappiness quotient is remarkably low—are you taking advantage of that, or have you fallen into old patterns of suck-it-up-and-bear-it self-pity?
And that’s the Public Service Announcement of the day, thank you.


