Using the Decorator Pattern to simplify your Rails models
The Decorator Pattern is a design pattern for augmenting an existing object with one or more additional methods. It’s an extremely useful tool for refactoring methods out of a complex class, and I personally feel that it’s grossly under-utilized by rails programmers.
Just about every rails application has to combine a handful of fields on a model in some human-friendly way. For example, say we want to represent a customer by their salutation, first name, last name, and email address everywhere they’re displayed in our system. Within the confines of the patterns rails provides you by default, your options are to (1) smash it all together in the view every time you display it; (2) create a method on the model; or (3) create a utility method in a helper.
All of these options are generally poor design choices, for various reasons:
- Leads to duplicate code. If the customer is displayed in more than one place, you’ve defined the formatting of their display name in multiple places.
- Quickly leads to very large models. Additionally, it defines something view-related just one level up from the database, which is a violation of all sorts of design principles.
- Helpers inevitably become grab bags of unrelated methods with no cohesion. It’s the best option of the three, but it still leads to pain eventually.
In this example, the combination of these four fields on the Customer class is very tightly bound to the Customer object itself. It does seem to make sense as a method on the Customer, but a database model is not a very good place to put for an arrangement of fields for a particular view.
Enter the Decorator.
A decorator wraps an existing object and augments it with one or more additional methods. In this case, the decorator would wrap a Customer, and provide a display_name method.
The implementation might look something like this:
class Decorator def initialize(decorated) @decorated = decorated end def method_missing(s, *a) @decorated.send(s, *a) end end class CustomerDecorator < Decorator def display_name "#{salutation} #{first_name} #{last_name} (#{email})" end end
And then this, somewhere in or near a view:
@decorated_customer = CustomerDecorator.new(@customer) # ... @decorated_customer.salutation @decorated_customer.display_name
The benefit of the decorator pattern is threefold:
- It provides an opportunity to move view-related methods off of the model
- It lets you put your models — generally the burliest of all rails objects — on a much-needed diet
- It is much easier to test than the same method directly on the model. I’ll blog on this in the near future.
Incidentally, the implementation of the decorator pattern in this post is a naive and simple one. If it seems useful to you, be sure to check out Draper, a much more complete application of the pattern, to see if it suits your needs better. If you do use draper, be sure to check out Ryan Bates’ excellent screencast on it at railscasts.com.
- Share this:
- Share