Вложенные атрибуты 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
опция, которая приводит к удалению записи.