Has_many_polymorphs for Real Polymorphism

Posted by labrat

UPDATE: Evan has updated has_many_polypmorphs so that child and parent can access each other both ways. For the average user, this means just use it like you normally would.

One of the things about being a rails beginner is that you don’t know whether you can’t do something efficiently because you don’t know how to do it or because Rails just doesn’t support it.

I think polymorphic associations as they currently stand are pretty limited or at least you could say they impose limitations. Just read Josh Susser’s blog for various examples.

That’s how I ended up arriving on the has\_many\_polymorphs doorstep. If polymorphic associations are the flexible joints of associations has\_many\_polymorphs takes it to a new level with joints that can flex 360 degrees in every direction.

It was hard to find friendly documentation suitable for a beginner but it really is simply once you have it. My example is probably one of the most basic uses but it’ll have to do. Check the documentation on Evan Weaver’s blog or fish through the plugin directory for examples within the tests(it’s pretty well-documented).

In my application I have Pictures and I have a bunch of other models that will use pictures like Profiles or Communities. This is easy to understand. One way to do it is documented in the wiki How to Use Polymorphic Associations. This document is a bit old so I’m not sure if it’s still the most efficient way of doing it but you end up with two models solely for tying together two different models. You need a Folder model and Linkings model. Not only that you have to set up all the association statements correctly and specify callbacks and other stuff to make them useful.

With has\_many\_polymorphs you’ll get all this for free.

It’s really easy with examples so here we go:

class Picture < ActiveRecord::Base
  has_many_polymorphs :consumers, :from => [:profiles, :communities], :through  => :picture_consumers
class PictureConsumer < ActiveRecord::Base
  belongs_to :picture
  belongs_to :consumer, :polymorphic => true
class Community < ActiveRecord::Base

This is what the PictureConsumer scheme looks like:

create_table "picture_consumers", :force => true do |t|
  t.column "picture_id",    :integer
  t.column "consumer_id",   :integer
  t.column "consumer_type", :string
  t.column "created_at",    :datetime, :null => false
  t.column "updated_at",    :datetime, :null => false
end

As you can see, the Community class (and neither does the profile class) require any association to be declared. This is because has\_many\_polymorphs takes care of it for us. Really, it’s that simple. With those you can now do:

@some_community.pictures
or
@some_picture.communities

You can associate pictures with as many models that you would like. Has\_many\_polymorphs also allows you to create joins that are polymorphic on both sides. Less configuration and more flexibility is certainly a plus.

One big snag waiting for you is the above may not work as is (especially in the controller). You need a declaration in application.rb and your environment file for Rails to properly load the associations.

For me these include the below. For application.rb:

require 'app/models/picture'
class ApplicationController < ActionController::Base

(update: no longer needed since update, see link below or note at the very top)

In development.rb for my environment: config.after_initialize do config.has_many_polymorphs_cache_classes = true end

This ensures the associations get loaded properly even when used in the console under development or for rendering in views.

UPDATE: Don’t do this either. It may unload some model methods after the second request. If you don’t know what that means you’re safe not putting the above in your environment file. Everything works fine out of the box thanks to Evan’s hard work.

The entirety of the information here was ripped out of Evan’s blog so please check it out and send him your regards. It’s marvellous work that I’m surprised is not already part of Rails.

has_many_polymorphs activerecord plugin :: evan weaver

has_many :through: The other side of polymorphic :through associations

has_many :through: Many-to-many Dance-off!

dependency injection for rails models :: evan weaver

Comments

Leave a response

  1. evanMarch 29, 2007 @ 07:48 PM

    You don’t even need to enable hasmanypolymorphscacheclasses anymore; it will reload properly without it. The only benefit to the caching now is request speed in development mode.

    Nice walkthrough, and thanks for your kind comment.

  2. labrat March 30, 2007 @ 02:49 AM

    Sorry for the confused comments I left on your blog. It turns out that your comment here was the solution. I just found out that now with autoloading, if you put the initialize hook to cache classes, it will unload all the methods on the second request (in the browser). Don’t know if that makes sense but I’ll have to change the above accordingly.

    In conclusion it means “everything works fine out the box” so thanks again!

  3. evanMarch 30, 2007 @ 03:13 PM

    That might be a bug. I’ll check it out.

  4. hooplaDecember 26, 2007 @ 02:18 PM

    please format your post so that it’s easier to read. i couldn’t get through it

Comment