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.







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