Active Record Associations and the Null Object Pattern

Posted by Craig Ambrose on September 22, 2006 at 09:55 PM

Many of you may be familiar with the Null Object Design Pattern. I can’t recall where I first encountered it, although I know that it is not in the gang of four’s “Design Patterns” book. A quick googling reveals this link which seems to explain the pattern quite well in detail.

To summarise, a null object is an object which we use when we have some sort of optional association, instead of returning a null (or in ruby, nil) value. Lets say that class Vehicle has a method which returns it’s engine (an instance of class Engine). The problem is, occasionally vehicles don’t have an engine, and so everywhere that we want to access the vehicle’s engine, we must check for nil, for example:


class Vehicle
def engine
#this method returns an engine, take my word for it
end def status
engine.nil? ? 'unknown' : engine.status
end

Not only does this status method seem uneccessarily complex, it also forced us to make a decision about what status string to return for a nil engine. That’s ok, but what if we have to do this somewhere else? Are we really making putting this constant in the right place?

Also, as we add more and more methods to Engine, this gets uglier. Vehicle ends up wrapping every method in engine, so that calling code wont accidentaly use a nil engine for something. This kind of encapsulation is a two edged sword. It’s good, because it helps decouple the contents of Vehicle (such as Engine) from the clients of Vehicle, allowing us to change the way vehicle works without breaking anything. It’s also well suited to unit testing. However, the trade off is that if you do this everywhere, you’re classes grow far too large.

In the case where we don’t want to wrap up engine, or we want to centralise our behaviour for not having an engine, the Null Object pattern comes in. We’re going to want an object which represents the nil engine, and does things like return “unknown” as it’s status. However, we don’t just use a special instance of class Engine, we actually create a new class, as some methods of engine might be computed, and we still want them to do nothing and return sensible null values.

In a strongly typed language, our classes Engine and the new NullEngine would both implement the same Interface, allowing us to use them interchangably. Ruby, however, doesn’t care about this, so the classes don’t need to be related at all, except in the eyes of the programmer.


class NullEngine
def status
'unknown'
end
endclass Engine
def status
#return real status value, perhaps from the database
end
end

I like to add a class method to Engine that returns a NullEngine object, for convenience. I also think that all NullEngine objects should equate as equal. You could either do this by making NullEngine a Singleton (so that only one instance ever exists), or by overriding it’s equality operator so that it always returned true when compared to other null objects. I prefer the latter, as it seems to be more foolproof.

The final remaining touch is to slot the object in so that it is used by Vehicle. In a rails app, Vehicle and Engine are likelly to be active record objects, and Vehicle probably belongs_to Engine. What we need to do is override the method that returns the engine, and returns a NullEngine if we don’t have a real one. We don’t want to get in the way of assignment of engines though.


class Engine
def self.null_object
NullEngine.new
end
endclass Vehicle
alias_method :original_engine, :engine def engine
original_engine.nil? ? Engine.null_object : original_engine
end
end

I’m afraid I can’t quite get out of the habit of calling it a Null Object, rather than a Nil Object to keep the other ruby folks happy, but it doesn’t much matter.

Now, clients of a Vehicle can go round calling vehicle.engine.status to their hearts content, or any other methods of engine, safe in the knowledge that they wont end up calling methods on a system Nil object. I used this pattern yesterday, and it cleanned up a whole lot of code for me. Don’t run around saying, “Craig said we don’t have to encapsulate aggregated objects anymore, let’s go crazy!”, but do consider how the Null Object pattern can work for you, particularly as in ruby, it’s just so damn easy.

Post continues, click to read more...

Comments: 10 (view/add your own) Tags: (none)

Redbox Release 2

Posted by Craig Ambrose on September 22, 2006 at 01:28 AM

Redbox is a rails compatible thickbox plugin. The original release is described Here .

Changes

I’ve finally been able to incorporate all the ideas and changes that have been submitted to me since releasing the redbox plugin. This new release breaks backward compatibility for one of the helper methods, and most of the javascript (although you probably weren’t accessing the JS directly). Major improvements are:

  • IE Compatibility. Yes that’s right, it works with the browser we all love to hate.
  • AJAX redboxes now accept the full range of link_to_remote parameters. See below for backwards compatibility issues.
  • Javascript cleanup. All the functions are now namespaced, and have their names camel cased.
  • Using rails javascript and content_tag helpers, to maximise compatibility with other plugins.
  • Several minor bugfixes.

Backward Compatibility Broken

If you are using the link_to_remote_redbox helper, you were previously passing in a hash of url_options. This has now been replaced with link_to_remote_options, so the following example:


<%= link_to_remote_redbox 'Click Here', :controller => 'test', :action => 'index' %>

becomes:


<%= link_to_remote_redbox 'Click Here', :url => {:controller => 'test', :action => 'index'} %>

Aside from url, you can use all the other options that link_to_remote normally takes, although I overwrite the :update option, and add to the :loading and :complete options. The main advantage of this change is being able to use the :method option, for simply restfull applications.

Updating

The demo is still here:
http://www.craigambrose.com/redbox_demo

You can install over using:
script/plugin install svn://rubyforge.org/var/svn/ambroseplugins/redbox

If you’re upgrading from the last release, you need to execute rake update_scripts from within the \vendor\plugins\redbox directory, (and restart your server proces) to ensure that the scripts are updated.

Thanks To

For testing, bug fixes, and code submissions, much credit goes to:

  • Brandon Keepers
  • Niko Dittmann
  • Randy Parker
  • Julien Coupard
  • Erin Staniland

Please keep the comments coming, and I’ll try hard to incorporate any changes.

Post continues, click to read more...

Comments: 163 (view/add your own) Tags: (none)