Unmaintainable templates result from the following:
- Markup repetition
- Logic in templates
Good designers repeat themselves (needed for a consistent design, good UX, etc), however good programmers don't.
To avoid markup repetition you should:
- Abstract interface components
- Figure out what is reused. Things like headers, footers, sidebars, navigation, breadcrumbs, ...
- Use partials
That's it!
Some problems with putting logic in your templates include:
- The markup can become highly repetitive
- Painful to test
Helpers
View helpers live in app/helpers
and provide small snippets of reusable code for views.
Helpers are not the end all, be all for logic in your views. Problems with helpers include:
- Big projects end up with tons
- Difficult to organize
- Complex logic isn't well suited for them
- Doesn't feel right
Decorator Pattern
How can I take my models and add behavior to them specific for the views? Enter the decorator pattern.
Decorators attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. ~ Design Patterns:Elements of Reusable Object-Oriented Software
Traits of a decorator:
- Wraps a single object
- Transparent interface
- Forwards methods to original object
In our use case, it adds presentational logic to models without affecting the model itself.
# Implementing a Decorator in Ruby
class Decorator
def initialize(component)
@component = component
end
def method_missing(method, *args, &block)
if @component.respond_to?(method)
@component.send(method, *args, &block)
else
super
end
end
# Recommended for proper etiquette
def respond_to_missing?(method, *)
@component.respond_to?(method) || super
end
end
# Usage
class UserDecorator < Decorator
def full_name
"#{@component.first_name} #{@component.last_name}"
end
# ... more presentational methods
end
When to decorate?
Think about if what you are doing relates directly to a single instance of a model. If it does, then you should go with decorators.
There's a gem for that. Draper.
Implementing basic decorators is easy, but Draper adds a few helpful features:
- Access to the view context
- Easily decorate collections
- Pretends to be the decorated object (helpful for
form_for
and such) - Easily decorate associations
That's not the end though. Having unique and/or complex UI behavior will quickly outgrow helpers and decorators.
Presenters
Sometimes what you want is an object that can represent the unique UI element and not the record it's displaying. Enter the presentation model.
The essence of a Presentation Model is of a fully self-contained class that represents all the data and behavior of the UI window, but without any of the controls used to render that UI on the screen. A view then simply projects the state of the presentation model onto the glass. ~ Martin Fowler
That is, dumb views.
TODO: Come up with an example of my own.
- Use i18n
- Find gems to do the work for you