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.