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