---
title: Renderable Null Objects
teaser:
tags: web,rails
author: George Brocklehurst
published_on: 2013-08-15
---

[We've talked about Null Objects before][1], and how they can remove unwanted
conditionals from your code. I'd like to talk about extending those benefits
into your Rails views.

## Setting the scene

Recently I've been working on an application that displays a lot of graphs of
financial data. A `DataSeries` can return a graph of its recent history, but
only if it `has_data?`.

    # app/models/users/data_series.rb
    class DataSeries < ActiveRecord::Base
      def recent_history_graph
        if has_data?
          Graph.new(self)
        end
      end

      def has_data?
        # ...
      end
    end

In the view, the graph is rendered like this:

    # app/views/data_series/show.html.erb
    <% if @data_series.recent_history_graph %>
      <%= render @data_series.recent_history_graph %>
    <% end %>

This code isn't *too* bad, but it could be better. The `has_data?` check is
already happening inside the `recent_history_graph` method, so the client code
doesn't need to know under what conditions a `DataSeries` can produce a `Graph`,
but it still needs to check if a graph was produced.

There are a lot of graphs in this application, and all these conditionals are
cluttering up my view code. They introduce additional [coupling][6] between the
view and the model; every time I call `recent_history_graph` I have to be aware
of two possible return types.

## Null Object to the rescue

By refactoring the code to use a Null Object I can remove all those pesky
conditionals once and for all.

First, I'll remove the conditional:

    # app/views/data_series/show.html.erb
    <%= render @data_series.recent_history_graph %>

Without checking for `nil`, whatever is returned by `recent_history_graph` is
passed to `render`, so my tests start yelling at me about trying to render
`nil`. To get around this problem I can change the `recent_history_graph` method
to make sure that it never returns `nil`:

    # app/models/data_series.rb
    class DataSeries < ActiveRecord::Base
      def recent_history_graph
        if has_data?
          Graph.new(self)
        else
          NullGraph.new
        end
      end

      # ...
    end

So far so good. Now my tests are yelling about a non-existent `NullGraph`
class, so to shut them up I can add an empty class:

    # app/models/null_graph.rb
    class NullGraph
    end

This is where things get really interesting. Instead of passing `nil` to render
I'm passing an instance of `NullGraph`. My tests reveal the following error:

> NullGraph is not an ActiveModel-compatible object that returns a valid
> partial path.

`render` can only handle objects that respond to the [`to_partial_path`][2]
method.  The usual solution to this error is to include the
[`ActiveModel::Conversion`][3] module into the class. It provides an implementation
of `to_partial_path` based on the class name. In this case it would return
`"null_graphs/null_graph"`. I don't want the default behaviour though; I don't
consider a `NullGraph` to be a first-class object, worthy of its own
sub-directory to keeps its views in. I'd rather use the partial `"graphs/null"`.
Fortunately, this can be achieved by defining my own `to_partial_path` method:

    # app/models/null_graph.rb
    class NullGraph
      def to_partial_path
        'graphs/null'
      end
    end

We're getting close! The tests are now complaining about a missing partial. The
partial itself doesn't need any code, but it's nice to leave a comment there to
guide future developers who stumble across a blank partial and wonder what on
Earth is going on:

    # app/views/graphs/_null.html.erb
    <%# Blank partial for rendering NullGraph objects %>

Run the tests one more time, and we're back to green with no more conditionals
cluttering the view code. Ah, that's better.

There's more that could be done: The logic for deciding what kind of `Graph` to
return probably doesn't belong in `DataSeries`, but I'll save that refactoring
for another day.

[1]: https://thoughtbot.com/blog/post/20907555103/rails-refactoring-example-introduce-null-object
[2]: http://api.rubyonrails.org/classes/ActiveModel/Conversion.html#method-i-to_partial_path
[3]: http://api.rubyonrails.org/classes/ActiveModel/Conversion.html
[6]: https://thoughtbot.com/blog/post/23112388518/types-of-coupling
