Polymorphic STI with associations

I have been working on a polymorphic STI schema where the inheriting classes have associations:

class Asset < ActiveRecord::Base end

class Sound < Asset   belongs_to :user, :polymorphic => true end

class User < ActiveRecord::Base   has_many :sounds end

The Asset class contains a 'type' attribute which is getting properly populated with the inheriting class's name, so that's fine, but I was getting an error that there was no 'user_type':

undefined method `user_type' for #<Sound:0x4cf3740>

OK, so I added 'user_type' to the Asset model and I can get past that error (which was only raised upon a :destroy for the object). However, now that I've added it, the user_type is not being populated when new objects are created/saved. Am I missing something?

-eric

The :polymorphic and the user_type field aren't necessary - polymorphic associations are used when you've got objects from multiple tables that all might be associated to a record, eg:

class Foo < AR::Base   belongs_to :things, :polymorphic => true end

class ThingA < AR::Base   has_many :foos, :as => :thing end

class ThingB < AR::Base   has_many :foos, :as => :thing end

Note that ThingA and ThingB are in seperate tables - if they were related via STI the :polymorphic wouldn't be needed.

--Matt Jones

Thanks for the reply, and I did leave a part out of my schema. There will be other models inheriting from Asset, such that ultimately there will be (at least):

class Asset < ActiveRecord::Base end

class Sound < Asset   belongs_to :user, :polymorphic => true end

class Video < Asset   belongs_to :user, :polymorphic => true end

class Image < Asset   belongs_to :user, :polymorphic => true end

class User < ActiveRecord::Base   has_many [:sounds,:videos,:images] end

The idea will be to have @user.videos, @user.images, etc., so the STI will handle the media types, and the polymorphism is used for User's relationship to each. However, currently user_id is not being saved through the association, and even if I manually set user_id in Asset (and user_type, just for fun), Video.first.user comes up nil. So, STI is working as expected, but the association is broken in some way that leads me to wonder whether Asset needs some connection with User (unlikely, given the docs and commentary I've been able to find), or whether the STI classes need some more connections to the base class in order for User to pass through the STI.

-eric

OK, so setting user_type to "User" manually in the base class does seem to work. I'm not sure why I couldn't find the user from (e.g.) @sound before, but that's water under the bridge now. The problem now seems to be that even though Sound[,Video,etc.] are polymorphic to User, the user_type attribute in the base class is not getting set when saved through an inheriting class. Why could this be? Is there some kind of accepts_nested_attributes_for catch that I'm not accounting for?

Alternatively, is there a way to see the mechanism by which the type field is set? More explicit logging, perhaps?

-eric

Not sure what the issue you're experiencing is - I'll have to mock some things up and try it. But I'll reiterate that the :polymorphic is not needed on those belongs_to declarations. The record that they point to (a User) is always in the same table. You wouldn't even need it going the other way (some kind of User belongs_to :media, for instance).

Oh, and that has_many isn't (AFAIK) valid syntax...

--Matt Jones

As my knowledge, it should be :

class Asset < ActiveRecord::Base    belongs_to :userable, :polymorphic => true

   def userable_type=(sType)        super(sType.to_s.classify.constantize.base_class.to_s)     end end

class Sound < Asset end

class User < ActiveRecord::Base   has_many :assets , :as=>:userable end

and in your assets table you must have 2 columns : userable_id and userable_type

best regards

As my knowledge, it should be :

[snip]

def userable_type=(sType) super(sType.to_s.classify.constantize.base_class.to_s) end end

Um, no. If you're needing to do this, you've done something seriously wrong. This is already supported by :polymorphic => true. Also note that your "solution" here will fail if the class associated to the table (Asset in this case) is more than one level above the class being stored (ie Asset > Sound > Mp3 will try to find records in a 'sounds' table => FAIL)

--Matt Jones

no, it still work event your class is inherited more than one level