Here’s a view serving monkeys#show . What’s wrong with it?

<% if @monkey . eating? %> <div class= "eating" > <%= @monkey . name -%> is eating. Nom nom </div> <% elsif @monkey . sleeping? %> <div class= "sleeping" > <%= @monkey . name %> cannot be bothered right now </div> <% end %> <div id= "banana-status" > <% if @monkey . bananas . any? %> <div class= "banana-count" > The monkey has had <%= @monkey . bananas_count %> bananas so far </div> <% else %> <div class= "hungry" > The monkey should really have a banana or two </div> <% end %> </div> <div id= "sidebar" > <div id= "favorite-dip-sauces" > <% if @monkey . bananas . any? %> <%= @monkey . bananas . map ( & :dip_sauce ). to_sentence -%> <% else %> Eat some bananas first <% end %> </div> </div>

There’s quite a bit of logic going on here. Monkeys have different states with both DOM classes and copy changes based on it.

Not only that, but banana consumption also introduces a few branches in our view code, increasing markup complexity and threatening maintainability. Now imagine this view is for a real app where it’s probably much larger and complex.

What if instead we were working with the following

<div class= " <%= @monkey . state_class -%> " > <%= @monkey . status_message -%> </div> <div id= "banana-status" > <div class= " <%= @banana . status_class -%> " > <%= @bananas . count_message -%> </div> </div> <div id= "sidebar" > <div id= "favorite-dip-sauces" > <%= @bananas . favorite_dip_sauces -%> </div> </div>

Curious? Good. Here’s how you do it:

Let’s look at how we can simplify this view by introducing a few objects that collaborate with monkeys and bananas to cleanly get at the required logic.

What we need is an object that behaves just like a monkey but with some added presentation behavior: a Decorator. A decorator allows you to add, replace or extend an object’s behavior based on its runtime characteristics without resorting to any metaprogramming hacks. It is an object that wraps another object adding any required functionality, and proxies the interface you care about to original decorated object.

We decorate a monkey object with the appropriate messages and DOM classes based on it’s state:

class MonkeysController < ApplicationController def show @monkey = decorated_monkey ( Monkey . find ( params [ :id ])) end protected def decorated_monkey ( monkey ) if monkey . eating? EatingMonkey . new ( monkey ) elsif monkey . sleeping? SleepingMonkey . new ( monkey ) else raise NoMoreMonkeysJumpingOnTheBed end end end

The implementation for the eating and sleeping monkeys are quite simple:

class EatingMonkey def initialize ( monkey ) @monkey = monkey end def state_class "eating" end def state_message " #{ @monkey . name } is eating. Nom nom." end def name @monkey . name end end class SleepingMonkey def initialize ( monkey ) @monkey = monkey end def state_class "sleeping" end def state_message " #{ @monkey . name } cannot be bothered right now" end def name @monkey . name end end

They are merely extending the monkey object with some view related logic. The view establishes a contract that that all monkey-like object must adhere to, but that’s it. Note that decorators also proxy message invokations to the wrapped object. In this case we’re forwarding the name method. Some folks will use method_missing to proxy all method calls over, but I’m of the mind that you should only expose the interface you care about and nothing more. This also helps document the actual contract established between the view and its collaborators.

The result is a view that is much smaller, easier to work with and extend. Not only that, but the system is now also trivial to test. Here’s an example for the state_message method on EatingMonkey

describe EatingMonkey do it "displays that it's eating on the state_message" do monkey = stub ( :monkey , name: 'George' ) EatingMonkey . new ( monkey ). state_message . should match /George.*eating/ end end

This test is great. It’s simple, verifies behavior and — because we pass in a thin test double — it informs us of the expected interface of the wrapped object: an unobstrusive thing that responds to name .

These are great improvements already, but we shouldn’t stop there. We still have some view logic around bananas that is used to control two sections of our view: the banana-status as well as the sidebar . The two cases we need to think about is when a monkey has eaten bananas, and when it hasn’t.

When it has eaten bananas, we can supply a wrapper object that helps present it. When it hasn’t, we have the special case of an empty collection. The NullObject pattern that you learned about on Josh’s post works well.

class MonkeysController < ApplicationController def show bananas = monkey . bananas if bananas . any? @bananas = Bananas . new ( bananas ) else @bananas = EmptyBananas . new end end end class Bananas def initialize ( bananas ) @bananas = bananas end def status_class "banana-count" end def count @bananas . size end def count_message "The monkey has had %{ count } bananas so far" end def favorite_dip_suaces @bananas . map ( & :dip_sauce ). to_sentence end end class EmptyBananas def status_class "hungry" end def count_message "The monkey should really have a banana or two" end def favorite_dip_suaces "Eat some bananas first" end end

We can also think of these improvements in terms of a few Object-Oriented design principles:

The system obeys the Tell don’t Ask principle where you should tell an object to do something as opposed to extracting data from it and implementing behavior on the caller code. Here, instead of asking a monkey if it is eating in order to display a state message, we tell it to give us the message simplifying and removing branches from the client code.

By not polluting the Monkey model, we’re also obeying the Single Responsability Principle where every object should have only one responsability and it should be encapsulated by one class. The Monkey class handles database finders, field abstraction, and data consistency via validations, not more than that. On the other hand, the decorators handle presentation related state and logic while delegating to Monkey when appropriate.

Finally, it obeys the Open/Closed Principle where each object is open for extension but closed for modification. There is no reason to modify the Monkey class in order to add the required behavior. Instead we extend a monkey’s behavior with another set of collaborating objects.

Decorators are not only useful as view cleanup, although it is a very common use case. Jeff Casimir’s draper is a gem that embodies the pattern and also allows you to invoke Rails’ view helpers by exposing the view context in your decorators.

But Decorators are useful in other scenarios as well. For example, you could use them to handle all the types of notifications should occur when saving a record without resorting to a nasty callback soup:

class UserCreatedNotifier def initialize ( saveable ) @saveable = saveable end def save Mailer . user_created ( @saveable ). deliver @saveable . save end end

Maybe in some cases an admin should be notified as well:

class UserCreatedAdminNotifier def initialize ( saveable ) @saveable = saveable end def save AdminMailer . user_created ( @saveable ). deliver @saveable . save end end

This frees your User model from complex state or parameter checking in an already complex callback chain, and it also allows you to easily add notification mechanisms to the user creation process, or even avoid notification altogether when that’s the need:

#notify nobody user . save # notify the user UserCreatedNotifier . new ( user ). save # notify the user and an admin UserCreatedNotifier . new ( UserCreatedAdminNotifier . new ( user )). save # notify the user and post to twitter TwitterNotifier . new ( UserCreatedNotifier . new ( user )). save # etc

Detect emerging problems in your codebase with Ruby Science. We’ll deliver solutions for fixing them, and demonstrate techniques for building a Ruby on Rails application that will be fun to work on for years to come.

Grab a free sample of Ruby Science today!