This post originated from an RSS feed registered with Ruby Buzz
by David Heinemeier Hansson.
Original Post: Finding the shortcut and using it effectively
Feed Title: Loud Thinking
Feed URL: http://feeds.feedburner.com/LoudThinking
Feed Description: All about the full-stack, web-framework Rails for Ruby and on putting it to good effect with Basecamp
Charles Miller examines the getter/setter syndrome in Java1 from a usage (rather than the normal construction) perspective and devices a shortcut. Instead of using individual access methods, he proposes the unified set_properties that takes a hash of attributes and automatically assigns them on the object instance. Unfortunately, he fails to see its uses and discards the idea with a clever Simpsons reference (we like those).
That's a shame because he had the right idea.
In constructing Rails, I reached exactly the same shortcut—albeit with slightly more natural Ruby-like semantics by using a signature of Object#attributes=(attributes). More importantly, I found an extremely handy use for it. Passing data from the view to the model in a MVC-style application usually means doing it twice. In the view with input tags as part of a form and in the controller by picking attributes from the request and calling access methods on the model. That's inefficient at best and error prone at worst.
Instead Rails adopts a combination of PHP-like input names that turns form data into hashes and the unified attribute assignment to do as follows:
<!-- The view -->
<form>
Name: <input type="text" name="user[name]">
Email: <input type="text" name="user[email]">
Website: <input type="text" name="user[website]">
</form>
# The controller action
def create_user
user = User.new
user.attributes = @params["user"]
user.save
end
As the form is submitted by the user, the Rails controller turns user[name], user[email], and user[website] into a hash that contains the name, email, and website as keys and the user supplied input as values. This hash can be accessed through @params["user"] from the action method.
Now the clever part is that the name, email, and website keys from the form maps to attributes with accessors on the model. This relieves the controller of a per-attribute knowledge responsibility, which allows us to add new fields on the view and model without changing the controller.
Actually, we only lavishly spent three lines in the controller to make it slightly easier to follow each step separately. Normally, it would likely look as follows (if there's to be no validation of the attributes):
def create_user
User.new(@params["user"]).save
end
Relief from per-attribute knowledge responsibility
In fact, the Rails framework goes even further in its attempt to relieve as many layers of per-attribute knowledge responsibility as possible. In your average web-application layer stack, it's not uncommon to have per-attribute knowledge spread across five concerns:
The form: One input field per attribute.
The action: The request is picked for attributes and they're assigned to the model.
The model: Each attribute is specified on the model with a type and boiler-plate serving of accessor methods.
The ORM: Column names are matched against model attributes and a type-conversion specified.
The database table: All persistable attributes in the model are given a column with a name and a type.
Everytime the model needs to evolve to either add, remove, or change attributes, you have to change each concern in a similar but distinct manner to move forward. That's an extremely cumbersome, time-consuming, and—again—error-prone dance of repetition.
Rails allows you to cut all that clutter in between the form and the database table and only work at the ends when the model needs to evolve. The action, model, and mapping steps are all handled by building on the knowledge already present in the form and in the database table.
So let's imagine that our lovely user from the example above needs to also be identified by a instant message handle. In Rails, this would require the following changes:
Addition to view: IM: <input type="text" name="user[im_handle]" />
Addition to database table: im_handle VARCHAR(100)
That's it. Now you're free to enjoy the rest of the Sunny day on the lawn with a drink and a hat while your lesser fortunate friends is caught in the toils of per-attribute knowledge spread across the entire layer stack. So much for low coupling, eh?
1. That onerous expenditure of 8-12 lines boiler-plate access code per attribute in stark contrast to Ruby's use of just 1 line for all attributes. An inefficiency that it shares with C#.