the STI-Polymorphic problem

Hi,

My first big Rails disappointment:

   single-table inheritance and polymporphic joins don't play nicely together.

On the rails-talk list Josh Susser was kind enough to tell me to steer clear of this combination as it "seems to be right up there with 'never get involved in a land war in Asia' as a classic blunder." He also said that the Rails implementation for STI and polymorphic joins area incompatible. I have been thinking about it and it seems like the concepts should be compatible. It also doesn't seem very cool that two Rails core features are incompatible. I'd like to know if there is potentially a way to patch the core so this isn't a problem.

For example, below is a test I wrote to work on this problem. These are my Active Record models and the database records.

Peter,

In general, STI and polymorphic joins work great togeher, as long as you always store the base class name as the type value, rather than the descendant class name. Thus, if you were to store Person (rather than Customer) as resource_type, you'd be fine.

- Jamis

Peter Michaux wrote:

Hi Jamis,

Thanks for the reply. I have tried what you said and it did work for me.

However it gets a messy when creating a new comment if some of the models that can have comments are using STI and some aren't or different depths of STI are being used (haven't tried the later). It becomes necessary to test for which model the comment is being created. If it is a Customer then use Person for resource_type. However if it is an Article then just use Article.

Also how do you get get around the Comment.find(1).resource giving back a Person instance when it really should give back a Customer instance?

Thank you, Peter

Peter Michaux wrote:

In general, STI and polymorphic joins work great togeher, as long as you always store the base class name as the type value, rather than the descendant class name. Thus, if you were to store Person (rather than Customer) as resource_type, you'd be fine.

Hi Jamis,

Thanks for the reply. I have tried what you said and it did work for me.

However it gets a messy when creating a new comment if some of the models that can have comments are using STI and some aren't or different depths of STI are being used (haven't tried the later). It becomes necessary to test for which model the comment is being created. If it is a Customer then use Person for resource_type. However if it is an Article then just use Article.

You can just do Customer.base_class (or Article.base_class) to obtain the base ActiveRecord class for a particular subclass.

Also how do you get get around the Comment.find(1).resource giving back a Person instance when it really should give back a Customer instance?

Note that AR::Base#find will always "do the right thing". In other words, if you do Person.find(1), and record #1 has a type of Customer, a Customer record will be returned. You'll get the right type back, not because of the Comment#resource_type column, but because of the Person#type column. Basically, the resource_type column is used _only_ to determine which table to pull the associated data from.

- Jamis

> > > In general, STI and polymorphic joins work great togeher, as long as you > > always store the base class name as the type value, rather than the > > descendant class name. Thus, if you were to store Person (rather than > > Customer) as resource_type, you'd be fine. > > However it gets a messy when creating a new comment if some of the > models that can have comments are using STI and some aren't or > different depths of STI are being used (haven't tried the later). It > becomes necessary to test for which model the comment is being > created. If it is a Customer then use Person for resource_type. > However if it is an Article then just use Article.

You can just do Customer.base_class (or Article.base_class) to obtain the base ActiveRecord class for a particular subclass.

Great! I added this to my comment class and it seems to work well

  def resource_type=(sType)     super(sType.constantize.base_class.to_s)   end

> Also how do you get get around the Comment.find(1).resource giving > back a Person instance when it really should give back a Customer > instance?

Note that AR::Base#find will always "do the right thing". In other words, if you do Person.find(1), and record #1 has a type of Customer, a Customer record will be returned. You'll get the right type back, not because of the Comment#resource_type column, but because of the Person#type column. Basically, the resource_type column is used _only_ to determine which table to pull the associated data from.

Really cool!

Jamis, thank you very much! I wasted a week on this problem trying to solve it in all sorts of bizarre ways. Now I have hope STI and polymorphic joins can do the job for me the way I was hoping.

Thanks again, Peter