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:

1
2
3
4
5
6
7
8
9
module ImpressionableMixin
  def self.included(base)
    base.instance_eval("has_many :impressions, :as => :impressionable")
  end
 
  def add_impression(visitor)
    Impression.create(:visitor => visitor, :impressionable => self)
  end
end

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:

1
2
3
4
5
6
7
class SomeModel < ActiveRecord::Base
  # associations ...
 
  include ImpressionableMixin
 
  # methods ...
end

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.

  • DZone
  • Twitter
  • Slashdot
  • Delicious
  • Digg
  • Technorati Favorites
  • Facebook
  • Reddit
  • StumbleUpon
  • LiveJournal
  • Squidoo
  • Google Bookmarks
  • LinkedIn
  • Share/Bookmark

5 Comments »

Stefan Kanev

July 31, 2009

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

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

Wojciech

August 2, 2009

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
end

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”:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Rails Metal Example #7: Tracking Analytics

August 10, 2009

[...] week ago, I posted Ruby on Rails: Polymorphic Associations with Mixin Modules which included an example of tracking impressions on different [...]

http://charlesmaxwood.com/ruby-on-rails-… « bst On Web Dev

August 17, 2009

[...] 12:53 am on August 18, 2009 Permalink | Reply Tags: app design, rails (9) http://charlesmaxwood.com/ruby-on-rails-polymorphic-associations-with-mixin-modules/ – Ruby on Rails: Polymorphic Associations with Mixin Modules [...]

Leave a comment