Rails Polymorphic has_many

maerics picture maerics · Jul 9, 2010 · Viewed 10k times · Source

Using Ruby on Rails, how can I achieve a polymorphic has_many relationship where the owner is always of a known but the items in the association will be of some polymorphic (but homogenous) type, specified by a column in the owner? For example, suppose the Producer class has_many products but producer instances might actually have many Bicycles, or Popsicles, or Shoelaces. I can easily have each product class (Bicycle, Popsicle, etc.) have a belongs_to relationship to a Producer but given a producer instance how can I get the collection of products if they are of varying types (per producer instance)?

Rails polymorphic associations allow producers to belong to many products, but I need the relationship to be the other way around. For example:

class Bicycle < ActiveRecord::Base
  belongs_to :producer
end

class Popsicle < ActiveRecord::Base
  belongs_to :producer
end

class Producer < ActiveRecord::Base
  has_many :products, :polymorphic_column => :type # last part is made-up...
end

So my Producer table already has a "type" column which corresponds to some product class (e.g. Bicycle, Popsicle, etc.) but how can I get Rails to let me do something like:

>> bike_producer.products
#=> [Bicycle@123, Bicycle@456, ...]
>> popsicle_producer.products
#=> [Popsicle@321, Popsicle@654, ...]

Sorry if this is obvious or a common repeat; I'm having surprising difficulty achieving it easily.

Answer

rewritten picture rewritten · Sep 28, 2012

You have to use STI on the producers, not on the products. This way you have different behavior for each type of producer, but in a single producers table.

(almost) No polymorphism at all!

class Product < ActiveRecord::Base
  # does not have a 'type' column, so there is no STI here,
  # it is like an abstract superclass.
  belongs_to :producer
end

class Bicycle < Product
end

class Popsicle < Product
end

class Producer < ActiveRecord::Base
  # it has a 'type' column so we have STI here!!
end

class BicycleProducer < Producer
  has_many :products, :class_name => "Bicycle", :inverse_of => :producer
end

class PopsicleProducer < Producer
  has_many :products, :class_name => "Popsicle", :inverse_of => :producer
end