Rails Polymorphic Association with multiple associations on the same model

In Rails 5 you have to define attr_accessor for :attachable_id and specify for relation :class_name and :foreign_key options only. You will get ...AND attachable_type = 'SecondaryPhoto' if as: :attachable used

class Post
  attr_accessor :attachable_id
  has_one :photo, :as => :attachable, :dependent => :destroy
  has_one :secondary_photo, -> { where attachable_type: 'SecondaryPhoto' }, class_name: "Photo", dependent: :destroy, foreign_key: :attachable_id

Rails 4.2+

class Photo
   belongs_to :attachable, :polymorphic => true
end

class Post
   has_one :photo, :as => :attachable, :dependent => :destroy
   has_one :secondary_photo, -> { where attachable_type: "SecondaryPhoto"},
     class_name: Photo, foreign_key: :attachable_id,
     foreign_type: :attachable_type, dependent: :destroy
end

You need to provide foreign_key according ....able'ness or Rails will ask for post_id column in photo table. Attachable_type column will fills with Rails magic as SecondaryPhoto


None of the previous answers helped me solve this problem, so I'll put this here incase anyone else runs into this. Using Rails 4.2 +.

Create the migration (assuming you have an Addresses table already):

class AddPolymorphicColumnsToAddress < ActiveRecord::Migration
  def change
    add_column :addresses, :addressable_type, :string, index: true
    add_column :addresses, :addressable_id, :integer, index: true
    add_column :addresses, :addressable_scope, :string, index: true
  end
end

Setup your polymorphic association:

class Address < ActiveRecord::Base
  belongs_to :addressable, polymorphic: true
end

Setup the class where the association will be called from:

class Order < ActiveRecord::Base
  has_one :bill_address, -> { where(addressable_scope: :bill_address) }, as: :addressable,  class_name: "Address", dependent: :destroy
  accepts_nested_attributes_for :bill_address, allow_destroy: true

  has_one :ship_address, -> { where(addressable_scope: :ship_address) }, as: :addressable, class_name: "Address", dependent: :destroy
  accepts_nested_attributes_for :ship_address, allow_destroy: true
end

The trick is that you have to call the build method on the Order instance or the scope column won't be populated.

So this does NOT work:

address = {attr1: "value"... etc...}
order = Order.new(bill_address: address)
order.save!

However, this DOES WORK.

address = {attr1: "value"... etc...}
order = Order.new
order.build_bill_address(address)
order.save!

Hope that helps someone else.


I have done that in my project.

The trick is that photos need a column that will be used in has_one condition to distinguish between primary and secondary photos. Pay attention to what happens in :conditions here.

has_one :photo, :as => 'attachable', 
        :conditions => {:photo_type => 'primary_photo'}, :dependent => :destroy

has_one :secondary_photo, :class_name => 'Photo', :as => 'attachable',
        :conditions => {:photo_type => 'secondary_photo'}, :dependent => :destroy

The beauty of this approach is that when you create photos using @post.build_photo, the photo_type will automatically be pre-populated with corresponding type, like 'primary_photo'. ActiveRecord is smart enough to do that.