1

This one has had me stumped all day!

I have the following models:

Pump class

class Pump < ApplicationRecord
  has_one :control, as: :equipment
  accepts_nested_attributes_for :control

Pump Schema

class CreatePumps < ActiveRecord::Migration[5.1]
  def change
    create_table :pumps do |t|
      t.references :property, foreign_key: true, null: false
      t.string :name, default: 'Pump', null: false

      t.timestamps
    end
  end
end

Control class

class Control < ApplicationRecord
  belongs_to :equipment, polymorphic: true

Control Schema

class CreateControls < ActiveRecord::Migration[5.1]
  def change
    create_table :controls do |t|
      t.belongs_to :device, foreign_key: true, index: true
      t.integer :position, index: true
      t.references :equipment, polymorphic: true, index: true
      t.belongs_to :control_type, foreign_key: true, index: true

      t.timestamps
    end
  end
end

I'm trying to update the association between a Control and a Pump. The following works:

[439] pry(main)> Pump.first.update!(control: Control.find(62))
.
.
.
=> true

But the following doesn't and I can't figure out why.

[438] pry(main)> Pump.first.update(control_attributes: {id: 62}) 
   (0.4ms)  BEGIN
   (0.4ms)  ROLLBACK
ActiveRecord::RecordNotFound: Couldn't find Control with ID=62 for Pump 
with ID=1
from /usr/local/bundle/gems/activerecord-
5.1.5/lib/active_record/nested_attributes.rb:584:in 
`raise_nested_attributes_record_not_found!'

The context is that I have a form for a Pump and when editing my Pump, there's a list of Controls in a select drop-down. I would just like to choose which Control is associated with the pump.

Update1: Answering a question from below

 [468] pry(main)> Pump.first.update(control_attributes: {id: 62})
  Pump Load (1.0ms)  SELECT  "pumps".* FROM "pumps" ORDER BY "pumps"."id" ASC LIMIT $1  [["LIMIT", 1]]
   (0.3ms)  BEGIN
  Control Load (0.4ms)  SELECT  "controls".* FROM "controls" WHERE "controls"."equipment_id" = $1 AND "controls"."equipment_type" = $2 LIMIT $3  [["equipment_id", 1], ["equipment_type", "Pump"], ["LIMIT", 1]]
   (0.3ms)  ROLLBACK
ActiveRecord::RecordNotFound: Couldn't find Control with ID=62 for Pump with ID=1
from /usr/local/bundle/gems/activerecord-5.1.5/lib/active_record/nested_attributes.rb:584:in `raise_nested_attributes_record_not_found!'
Mr Citizen
  • 37
  • 1
  • 5

3 Answers3

0

When you use accepts_nested_attributes_for for linked model, it will create new records when attributes are provided without id parameter. And it will update existing record linked with the parent record, when attributes are provided with id parameter.

ActiveRecord::RecordNotFound: Couldn't find Control with ID=62 for Pump with ID=1 : This error states that there is no Control record found for Pump object with the mentioned Ids.

You can add new control record for pump as:

Pump.first.update(control_attributes: { attribute1: 'attribute1_value' } )

This will create a new Control record associated with the Pump object having Id 1. And now you can update this again as follows:

Pump.first.update(control_attributes: { id: 1, attribute1: 'updated_attribute1_value' } )

Note that id of the newly created Control record is taken as 1.

Please read through the documentation to get more details.

Hope this helps !

Ashik Salman
  • 1,819
  • 11
  • 15
0
Pump.first.update(control_attributes: {id: 62}) 

Rails's nested attributes does't work this way! The code above means:

Find a control which's id is 62, and it's equipment_type should be "Pump", and it's equipment_id should be Pump.first.id, then update with extra params, which you did not provided.

You got this error because in the first step, the control with id 62, it's equipment_id isn't Pump.first.id

Like, to update name of the control which's id is 60, belongs to Pump.first, in correct association:

Pump.first.update(control_attributes: {id: 60, name: "xxxx"}) 
lei liu
  • 2,735
  • 1
  • 16
  • 18
  • Thanks @lei-liu. I came to the same conclusion that 'accepts_nested_attributes_for' only allows you to update the attributes of a model with an existing relationship (or create a new one) and **not** establishing a new relationship. I found the following reply to another question useful in what I'm trying to do - https://stackoverflow.com/a/38439414/9520908 – Mr Citizen Mar 21 '18 at 22:54
0

You could override the nested attributes setter method in the model so that it also updates the foreign key column directly.

# pump.rb
def control_attributes=(attributes)
  if (new_control = Control.find_by(id: attributes[:id]))
    self.control_id = new_control.id
  end

  super
end

Note: be careful about assigning the relation directly (i.e self.control = new_control) because that could result in some unexpected side effects if it is a has_one association defined with a :dependent option that results in deleting the record.