Polymorphic Associations and Interfaces In Ruby/Rails

I have been using a lot of polymorphic associations in rails recently. If you don’t know what they are, bear with me and I will explain in a moment. To really use a polymorphic association properly it’s vital to understand interfaces, which should be part of your daily bread and butter toolkit, but in a dynamic language like Ruby they are a conceptual construct, rather than a language enforceable construct, like in Java or C#.

So before I go on about interfaces, let me give you a quick overview of polymorphic associations.

A polymorphic association lets you associate your model to another object, model, entity etc (pick your word, they all mean the same thing really) without knowing its type. Think about a user holding a number of subscriptions, one might be an RSS feed, another a stream of Twitter message (via some fancy new API), and a third a binary feed representing your CPU usage for the last 20 minutes.

If you want to use polymorphic associations with this, you could do it like this:

class User < ActiveRecord::Base
  has_many :subscriptions
end

class Subscription < ActiveRecord::Base
  belongs_to :producer, :polymorphic => true
end

class RSSFeed  < ActiveRecord::Base
  has_many :subscribers, :as => :producer, :class_name => "subscription"
end

class TwitterFeed  < ActiveRecord::Base
  has_many :subscribers, :as => :producer, :class_name => "subscription"
end

class CPUMonitor  < ActiveRecord::Base
  has_many :subscribers, :as => :producer, :class_name => "subscription"
end

At the database level, the subscriptions table will have two columns for the association: producer_type and producer_id. A composite foreign key. A row in the subscriptions table will show something like producer_type = “CPUMoniter” and producer_id = 3. When you call the producer method in subscription it will load the row in the CPUMoniters table with ID 3.

So the polymorphic bit means that the type of thing you’re going to get back when you ask a subscription for its producer is unknown. It might be an RSSFeed, it might be a TwitterFeed, or it might be a CPUMoniter, or possibly anything else you can think of.

More formally, according to Wikipedia (which knows all) polymorphism “is the ability of objects belonging to different types to respond to method calls of the same name, each one according to an appropriate type-specific behaviour.”.

Put in dynamic, Ruby terms, this means I don’t care what I sort of model I get when I ask a subscription for its producer, as long as it goes quack I can treat it like it’s a duck.

This is where interfaces come in. Interfaces specify what I want the returned object to behave like, it could be a duck, or it could be a spaceship or maybe, just maybe, it could be a producer. In fact if you read though the ActiveRecord docs on polymorphic associations, you will find that “interface” is exactly what they call the parameter passed in the :as key of the params hash, and the first parameter to a polymorphic belongs_to association, and even the xxx_type and xxx_id columns in the database. This threw me quite a bit when I first looked at polymorphic associations, you have to declare that the association uses the producer interface, but you don’t have a producers model, or a producers table, or a producers anything for that matter. To my mind, “producers” is the name of the interface that RSSFeed, TwitterFeed and CPUMonitor all have to implement. It could specify that all of them must have a “next_message” method, which will give me (surprise surprise) the next message they produce. If I don’t have something like this, then my code is going to get messy:

class Subscription  < ActiveRecord::Base
  belongs_to :producer, :polymorphic => true

  def print_next_message
    if producer.is_a?(RSSFeed)
      puts producer.rss_message
    elsif producer.is_a?(TwitterFeed)
      puts producer.twitter_messsage
    else
      puts producer.cpu_message
    end
  end
end

Which is just really butt ugly. Its slow to grok to figure out what’s going on, and I don’t even want to think about maintaining that code when I add a CriticalNuclearReactor class that people can subscribe to. If on the other hand, I say that every thing which implements the producer interface must define a next_message method I can push the ugliness into the classes that implement the interfaces.

class Subscription  < ActiveRecord::Base
  belongs_to :producer, :polymorphic => true

  def print_next_message
    puts producer.next_message
  end
end

class RSSFeed  < ActiveRecord::Base
  has_many :subscribers, :as => :producer, :class_name => "subscription"

  def next_message
    rss_message
  end
end

class TwitterFeed  < ActiveRecord::Base
  has_many :subscribers, :as => :producer, :class_name => "subscription"

  def next_message
    twitter_message
  end
end

class CPUMonitor  < ActiveRecord::Base
  has_many :subscribers, :as => :producer, :class_name => "subscription"

  def next_message
    cpu_message
  end
end

class CriticalNuclearReactor  < ActiveRecord::Base
  has_many :subscribers, :as => :producer, :class_name => "subscription"

  def next_message
    get_the_bloody_hell_out_of_here_message
  end
end

Just look at how much easier the print_next_message method in subscription is to read. This is good. Also notice how the only time I need to care what the technique for getting a message from a nuclear reactor is when I’m inside the nuclear reactor class, which means I’m already thinking about nuclear stuff. I no longer need to hold that in my stack when I’m working on how subscriptions work. Finally, my subscription model does not need to be changed when I add another producer type. This is important. Very important! As soon as anyone with even a mild case of featuritus gets near your code they are going to want to add stuff, and more often that not, that will mean adding new classes. If adding new classes means adding new branches to all your conditional logic, your stuffed. In that case write me a big cheque and I might come and fix your design for you.

So this is how interfaces work. In a static language (Java, C#) you can get the language to enforce this:

public interface Producer {
  public message next_message();
}

and your subscription class can (is required to) state its the type it expects for producer

public class Subscription{
  public Producer _producer;
}

and your compiler checks all this for you. If you get it wrong you get nice early TypeMismatchError. Now I’ve finally decided I prefer dynamic languages to static, so I’m not going to claim Ruby is crap for not allowing this. But it is a big stinking black hole you can fall into if your not expecting it.

In fact it’s not really a problem at all. All you have to do is write down, somewhere obvious, somewhere where anyone in your team is going to find it, what methods you expect producer to implement. If you can find a way to check what types could have been assigned to the producers association (analysing the code is too hard, but you could get a good idea by peeking into the database) you can even get your unit tests to check that all the classes that are going to be returned from producer respond to the next_method message. You could even write a module that you import into your producer classes that provides either a decent default implementation or raises “Interface Not Implemented Properly, shoot the coder” exceptions.

So that’s interfaces. Hopefully you know have a good idea of why they are very important when using polymorphic associations, even though you can’t code them (in Ruby), you should be thinking about them. Really.

By |April 30th, 2008|General|6 Comments