Вложенные атрибуты Rails и изменение ассоциаций

Это меня весь день ставило в тупик!

У меня есть следующие модели:

Класс насоса

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

Схема насоса

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

Контрольный класс

class Control < ApplicationRecord
  belongs_to :equipment, polymorphic: true

Схема управления

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

Я пытаюсь обновить связь между Управлением и Насосом. Следующие работы:

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

Но следующее не делает, и я не могу понять, почему.

[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!'

Контекст заключается в том, что у меня есть форма для насоса, и при редактировании моего насоса в раскрывающемся списке выбора отображается список элементов управления. Я просто хотел бы выбрать, какое управление связано с насосом.

Обновление 1: отвечая на вопрос снизу

 [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!'

3 ответа

Решение
Pump.first.update(control_attributes: {id: 62}) 

Вложенные атрибуты Rails не работают таким образом! Код выше означает:

Найдите элемент управления с идентификатором 62, и его equipment_type должен быть "Pump", а его equipment_id должен быть Pump.first.id, затем обновите его дополнительными параметрами, которые вы не предоставили.

Вы получили эту ошибку, потому что на первом шаге элемент управления с идентификатором 62, его equipment_id не является Pump.first.id

Мол, чтобы обновить имя элемента управления с идентификатором 60, принадлежащего Pump.first, в правильной ассоциации:

Pump.first.update(control_attributes: {id: 60, name: "xxxx"}) 

Когда вы используете accepts_nested_attributes_for для связанной модели, он будет создавать новые записи, когда атрибуты предоставляются без id параметр. И он обновит существующую запись, связанную с родительской записью, когда атрибуты будут предоставлены id параметр.

ActiveRecord::RecordNotFound: Couldn't find Control with ID=62 for Pump with ID=1: Эта ошибка говорит о том, что нет Control найдена запись для Pump объект с указанными идентификаторами.

Вы можете добавить новую контрольную запись для насоса как:

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

Это создаст новый Control запись, связанная с Pump объект с идентификатором 1, И теперь вы можете обновить это снова следующим образом:

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

Обратите внимание, что id вновь созданной контрольной записи принимается за 1,

Пожалуйста, прочитайте документацию, чтобы получить более подробную информацию.

Надеюсь это поможет!

Вы можете переопределить метод установки вложенных атрибутов в модели, чтобы он также напрямую обновлял столбец внешнего ключа.

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

  super
end

Примечание: будьте осторожны при прямом назначении отношения (т. Е. self.control = new_control), потому что это может привести к некоторым неожиданным побочным эффектам, если это ассоциация has_one, определенная с помощью :dependent опция, которая приводит к удалению записи.

Другие вопросы по тегам