Announcing has_named_bootstraps (June 26, 2010)
Most nontrivial Web apps will require some amount of bootstrap data to be present in the database. Classic examples are lists of countries, US states, or postal codes. In my work at Children's Hospital Boston, we frequently deal with lists of clinics or diagnoses. If you're using role based access control (RBAC), you'll need different roles to be preloaded.
You might write part of the logic for RBAC like this:
class AdminController < ApplicationController
before_filter :require_admin_user
...
private
def require_admin_user
admin_role = Role.find_by_name 'admin'
unless current_user.roles.include?(admin_role)
flash.now[:error] = "You're not authorized to do that."
redirect_to peon_path
end
end
But what's wrong with doing it this way?
- You end up repeating
Role.find_by_name(...)a million times. Of course, in practice, you'd write methods with names likeis_admin?,is_moderator?,is_regular_user?. But these all have the same structure and vary only in what role they're concerned with, indicating that we have a chance to DRY things up. - You're hardcoding strings. One of the first things I learned in programming, and quite likely one of the first things you learned, is that this always leads to trouble eventually. A role's name will change, or you'll type "administrator" where you meant "admin", or any of innumerable variations.
- You'll often want to refer to values bootstrapped in this way all together, perhaps in a
<select>or a listing on an index page. Of course you can alwaysfind(:all)and grab every instance of that model, but I find that you often want to treat the bootstrapped values differently from other instances that might get created.
Dan Chak, in his book Enterprise Rails, suggests that you create a constant to refer to this kind of data. I don't agree with everything in this book, but I do believe that this particular advice is on the money. So our example from above might turn into:
def require_admin_user
unless current_user.roles.include?(Role::ADMIN)
flash.now[:error] = "You're not authorized to do that."
redirect_to peon_path
end
end
This could still be DRYer, but it's definitely a step in the right direction.
Having defined a few gazillion of these constants in some of my code, I wrote and recently open-sourced a gem called has_named_bootstraps (with source on GitHub) that automates defining these constants. There are plenty of options to play with, but the most basic case just looks like:
class Department < ActiveRecord::Base
has_named_bootstraps(
:R_AND_D => 'R&D',
:MARKETING => 'Marketing',
:HANDSOME_DEVILS => 'Web Development'
)
end
The constants R_AND_D, MARKETING, and HANDSOME_DEVILS are now all defined on Department.
Feedback, bug reports and pull requests are all most welcome. I'm eager to hear what you think.
blog comments powered by DisqusAll content of this site copyright © 2009-2010 Phil Darnowsky. Released under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported license. Some links in this blog may be affiliate links, which pay a small sales comission.