yesterday i faced a problem with my rails application, my models was as follows
class Vehicle < ActiveRecord::Base
MOVABLES = [:car, :bus, :plane]
belongs_to :movable, polymorphic: true, dependent: :destroy, required: true, autosave: true
accepts_nested_attributes_for :movable
end
class Car < ActiveRecord::Base
has_one :vehicle, dependent: :destroy, as: :movable
end
class Bus < ActiveRecord::Base
has_one :vehicle, dependent: :destroy, as: :movable
end
class Plane < ActiveRecord::Base
has_one :vehicle, dependent: :destroy, as: :movable
end
so, when i try to create a Veihcle with nested attributes for the movable
it complain
with error “are you trying to build a polymorphic relation?” and i thought it will continue
my code was as follows
Veihcle.create! name: 'my car', movable_type: 'Car', movable_attributes: { color: red }
the previous code should create a Veihcle with extended attributes of a car with color = red, so i had to google first and guess what is the first thing to find?, Yup it is Stackoverflow
the highly rated solution didn’t work for me :
class Job <ActiveRecord::Base
belongs_to :client, :polymorphic=>:true
attr_accessible :client_attributes
accepts_nested_attributes_for :client
def attributes=(attributes = {})
self.client_type = attributes[:client_type]
super
end
def client_attributes=(attributes)
some_client = self.client_type.constantize.find_or_initilize_by_id(self.client_id)
some_client.attributes = attributes
self.client = some_client
end
end
so i tried this solution
class Job <ActiveRecord::Base
belongs_to :client, :polymorphic=>:true, :autosave=>true
accepts_nested_attributes_for :client
def attributes=(attributes = {})
self.client_type = attributes[:client_type]
super
end
def client_attributes=(attributes)
self.client = eval(type).find_or_initialize_by_id(attributes.delete(:client_id)) if client_type.valid?
end
end
and i had to modify it to suite my code like so
class Vehicle < ActiveRecord::Base
MOVABLES = [:car, :bus, :plane]
belongs_to :movable, polymorphic: true, dependent: :destroy, required: true, autosave: true
accepts_nested_attributes_for :movable
def attributes=(attributes = {})
self.movable_type = attributes[:movable_type]
super
end
def movable_attributes=(attributes)
self.movable = eval(type).find_or_initialize_by_id(attributes.delete(:movable_id)) if movable_type.valid?
end
end
but yeah, that didn’t work either so i got the idea from the previous code movable_attributes=
is the method that is invoked
when assigning the the mobable_attributes hash, so i have to override it to create/update the current child as follows
class Vehicle < ActiveRecord::Base
MOVABLES = [:car, :bus, :plane]
belongs_to :movable, polymorphic: true, dependent: :destroy, required: true, autosave: true
accepts_nested_attributes_for :movable
def movable_attributes=(attributes)
if MOVABLES.include?(movable_type.underscore.to_sym)
self.movable ||= self.movable_type.constantize.new
self.movable.assign_attributes(attributes)
end
end
end
that will create a movable object if it doesn’t exist, and it’ll update it in case it already exists.
i wish rails would behave like this by default, and i have no idea why it doesn’t do that as it is a trivial solution.