accepts_nested_attributes_for with belongs_to polymorphic
I've also had a problem with the "ArgumentError: Cannot build association model_name. Are you trying to build a polymorphic one-to-one association?"
And I found a better solution for this kind of problem. You can use native method. Lets look to the nested_attributes implementation, inside Rails3:
elsif !reject_new_record?(association_name, attributes) method = "build_#{association_name}" if respond_to?(method) send(method, attributes.except(*UNASSIGNABLE_KEYS)) else raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?" endend
So actually what do we need to do here? Is just to create build_#{association_name} inside our model. I've did totally working example at the bottom:
class Job <ActiveRecord::Base CLIENT_TYPES = %w(Contact) attr_accessible :client_type, :client_attributes belongs_to :client, :polymorphic => :true accepts_nested_attributes_for :client protected def build_client(params, assignment_options) raise "Unknown client_type: #{client_type}" unless CLIENT_TYPES.include?(client_type) self.client = client_type.constantize.new(params) endend
I finally got this to work with Rails 4.x. This is based off of Dmitry/ScotterC's answer, so +1 to them.
STEP 1. To begin, here is the full model with polymorphic association:
# app/models/polymorph.rbclass Polymorph < ActiveRecord::Base belongs_to :associable, polymorphic: true accepts_nested_attributes_for :associable def build_associable(params) self.associable = associable_type.constantize.new(params) endend# For the sake of example:# app/models/chicken.rbclass Chicken < ActiveRecord::Base has_many: :polymorphs, as: :associableend
Yes, that's nothing really new. However you might wonder, where does polymorph_type
come from and how is its value set? It's part of the underlying database record since polymorphic associations add <association_name>_id
and <association_name>_type
columns to the table. As it stands, when build_associable
executes, the _type
's value is nil
.
STEP 2. Pass in and Accept the Child Type
Have your form view send the child_type
along with the typical form data, and your controller must permit it in its strong parameters check.
# app/views/polymorph/_form.html.erb<%= form_for(@polymorph) do |form| %> # Pass in the child_type - This one has been turned into a chicken! <%= form.hidden_field(:polymorph_type, value: 'Chicken' %> ... # Form values for Chicken <%= form.fields_for(:chicken) do |chicken_form| %> <%= chicken_form.text_field(:hunger_level) %> <%= chicken_form.text_field(:poop_level) %> ...etc... <% end %><% end %># app/controllers/polymorph_controllers.erb...private def polymorph_params params.require(:polymorph).permit(:id, :polymorph_id, :polymorph_type) end
Of course, your view(s) will need to handle the different types of models that are 'associable', but this demonstrates one.
Hope this helps someone out there. (Why do you need polymorphic chickens anyway?)
The above answer is great but not working with the setup shown. It inspired me and i was able to create a working solution:
works for creating and updating
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 endend