You probably don’t write code like this:
if object.kind_of?(User)
do_this
else
object.do_that
end
Why not? Because Ruby encourages duck typing and polymorphism.

Here’s the same principle:
if object.nil?
do_this
else
object.do_that
else
This checks that object is of type NilClass instead of type User.
There’s an old pattern called Null Object that addresses this special case of avoiding type-checking in favor of duck typing.
Here’s an example of the “Introduce Null Object” refactoring to fix this problem with a Null Object in a Rails app.
Airbrake reports an error from this line in a Haml view:
= l location.orders.ascend_by_created_at.first.created_at.to_date
Demeter is displeased but let’s fix the problem for our users first.
We could do something like this:
- if location.orders.any?
= l location.orders.ascend_by_created_at.first.created_at.to_date
- else
No orders yet
Resist the urge.
First, re-create the error in the setup phase of a functional or integration test by creating a location without orders, then making an HTTP GET to the page.
We can change the view:
= l location.first_order_date
Much better. Bonus: we’ve given the line an intention-revealing name.
This new method doesn’t exist yet, though, so let’s test-drive it:
context 'first_order_date' do
setup do
@location = create(:location)
end
context 'when an order exists' do
setup do
@date = Date.today
create :order, location: @location, created_at: @date
end
should 'return first order date' do
assert_equal @date, @location.first_order_date
end
end
context 'when no orders exist' do
should 'respond to strftime' do
assert_respond_to @location.first_order_date, :strftime
end
end
end
The interesting bit is the ‘no orders’ case. Instead of returning nil, which caused the original error, we want to return an object that responds to strftime.
Why? Let’s look at the view again:
= l location.first_order_date
That l method is an alias for localize, which calls I18n.translate. If we look inside that method, we’ll see something like:
def localize(locale, object, format = :default, options = {})
unless object.respond_to?(:strftime)
raise ArgumentError
end
# ...
end
The localize method expects an object that responds to strftime.
So, to get the unit test green, let’s do this in the Location model:
def first_order_date
if first_order
first_order.created_at.to_date
else
NullDate.new 'orders'
end
end
private
def first_order
@first_order ||= orders.ascend_by_created_at.first
end
What’s this hipster NullDate object? We’ll have to write it.
The failing test for NullDate tests its strftime method:
context 'strftime' do
setup do
@null_date = NullDate.new('orders')
end
should 'return user-friendly message' do
assert_match /No orders yet/, @null_date.strftime('anything')
end
end
The passing implementation:
class NullDate
def initialize(nullable)
@nullable = nullable
end
def strftime(string)
I18n.t 'models.null_date.strftime', nullable: @nullable
end
end
Since the intention of this feature is to display copy in the view via the localize method, we localized the response of NullDate#strftime.
In config/locales/en.yml:
en:
models:
null_date:
strftime:
No %{nullable} yet
So that works. Is it worth it? I say yes:
Here’s a view serving monkeys#show. What’s wrong with it?
<h1><%= @monkey.name -%>'s Monkey page</h1>
<% if @monkey.eating? %>
<div class="eating">
<h2><%= @monkey.name -%> is eating. Nom nom.</h2>
</div>
<% elsif @monkey.sleeping? %>
<div class="sleeping">
<h2><%= @monkey.name %> cannot be bothered right now</h2>
</div>
<% end %>
<div id="banana-status">
<% if @monkey.bananas.any? %>
<div class="banana-count">
<p>The monkey has had <%= @monkey.bananas_count %> bananas so far</p>
</div>
<% else %>
<div class="hungry">
<p>The monkey should really have a banana or two</p>
</div>
<% end %>
<div id="sidebar">
<div id="favorite-dip-sauces">
<% if @monkey.bananas.any? %>
<p><%= @monkey.bananas.map(&:dip_sauce).to_sentence -%></p>
<% else %>
<p>Eat some bananas first</p>
<% 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 -%>">
<h2><%= @monkey.status_message -%></h2>
</div>
<div id="banana-status">
<div class="<%= @banana.status_class -%>">
<p><%= @bananas.count_message -%></p>
</div>
</div>
<div id="sidebar">
<div id="favorite-dip-sauces">
<p><%= @bananas.favorite_dip_sauces -%></p>
</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:
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
It is a bad idea to do a switch based on an attribute of another object. If you must, it should be on your own data, not someone else’s. - Refactoring by Martin Fowler
—
We’re reading Refactoring as part of an internal book club. This quote reminds me of the Feature Envy code smell.
class Cart
def price
@item.price + @item.tax
end
end
If most of the work in a method is being done on another object (@item), the method should be on that other object.
If you haven’t played with Kevin Rutherford’s Reek gem before, it’s a neat way to find smells like Feature Envy in your Ruby code.
This the first in a series of short videos. They feature Blake Mizerany discussing Sinatra and Heroku in great technical detail at September’s Boston.rb.
Blake Mizerany wrote Sinatra in 2006 because he was working on a high traffic site with a lot of POSTs, PUTs, and DELETEs. GETs can be cached but the others cannot.
In this video, Blake discusses the following concepts as they apply to Sinatra:
Start with a string, start with a text file, use main on Ruby. Move up to Factories and Model-View-Controller and ActiveRecord when you need it.
Rails creates 72 files when you run the rails command. Blake decided he didn’t need to start with MVC on each new project.
Blake uses Sinatra often for web services. He compares Rails’ respond_to with Sinatra’s content_type declaration. He discusses his feeling that the routing systems in other frameworks are overly complex undesirable.
get '/users.json' do
content_type :json
@user = User.find(params[:id])
@user.to_json
end
His first example, shown above, highlights Sinatra’s clean content type approach.
In the next video, Blake discusses:
Accepts header using Rack middlewareI’m in the middle of working on some design concepts for another shotgunflat/thoughtbot joint, and for the first time in a very long time, the design started looking vaguely familiar—as it was unfolding. It was quite arresting at first. The thing that made it even more strange was that once I figured out where I recognized the design, it dawned on me that I was essentially plagiarizing work from my own past. It wasn’t intentional, it just sort of happened. The design from the past is not in use, so it’s not a very big deal, but it kind of caught me off guard.
I’ll have to change it to keep myself happy, but I wonder if there is any kind of ethical issue with this. Probably not, but an interesting situation nonetheless.