Emad Elsaid Rails Accepts_nested_attributes for polymorphic relation solution

Tuesday 11 October 2022

#ruby

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.

Backlinks

🌻 Home

See Also

Cloning All Your GitHub Repositories or Updating Them in One Command
Deploy rails application with docker compose and capistrano
Download RSS Offline
ERB Templates as Standalone Executables
Enforcing Project Structure With Rspec
Git as Messages Queue
🌻 Home
Implementing Hover Cards With Minimum Javascript
Integerating Jekyll and Octopress in Emacs
⌨️ Programming
Projects Files Flame Graph
Representing Named Entity Mention in Postgres for Fast Queries
Tracing Ruby Applications Execution in 4 Lines