Ruby on Rails: Polymorphic Associations with Mixin Modules

I work for a lead generation company. Part of the application I’m working required us to know how many people viewed a particular item in the system. So, my co-worker and I created an Impression model that tracked each appearance of the associated object. We created a polymorphic association to allow impressions to be gathered on several different models.

The trick was that we found ourselves writing the same functionality on each of the models that needed impressions gathered. So, we took that functionality as well as the has_many polymorphic association and moved them into a module. Here’s what the module looks like:

module ImpressionableMixin
  def self.included(base)
    base.instance_eval("has_many :impressions, :as => :impressionable")

  def add_impression(visitor)
    Impression.create(:visitor => visitor, :impressionable => self)

This bit of code does two things for us. First, it creates the has many association on the model with the ImpressionableMixin module is included. Second, it adds the functionality related to the association to the model, which allows us to keep our code DRY. In other words, our models look like this:

class SomeModel < ActiveRecord::Base
  # associations ...

  include ImpressionableMixin

  # methods ...

I’ve left the module’s file in /app/models as it gets loaded when the models are loaded. This allows Rails to load it up when it loads all of the models.

I really like the encapsulation it provides for the functionality and the clean interface it provides.

{ 4 comments… read them below or add one }

Stefan Kanev July 31, 2009 at 10:59 am

You don’t need the instance eval. You can simply do base.has_many

I tend to end up doing this every time I use a polymorphic relationship


drool July 31, 2009 at 12:25 pm

This is how DataMapper suggests to handle polymorphic relationships within it (as opposed to using STI).


Wojciech August 2, 2009 at 11:39 pm

add_impression isn’t necessary, as ActiveRecord’s association proxies provide us with some methods like:

some_record.impressions.create :visitor=>visitor

– no need to pass :impressionable=>self

Of course you can wrap this in a custom method for abstraction:

def impression( visitor )
self.impressions.create :visitor=>visitor

But I recommend against creating such abstractions until it’s necessary – there’s a good chance ActiveRecord associations will do. You can even extend them, take a look at “association extensions”:


Ali October 21, 2011 at 10:00 am

Very useful, thanks for sharing, I would suggest using a block rather than a string:

def self.included(base)
base.instance_eval { has_many :impressions, :as => :impressionable }


Leave a Comment

{ 3 trackbacks }