Как добавить существующие записи в has_many без его сохранения в БД сразу?
Предположим, у меня есть это:
class Pirate < ActiveRecord::Base
has_many :parrots
validates_presence_of :name
end
class Parrot < ActiveRecord::Base
belongs_to :pirate
end
И у меня есть существующие пираты и попугаи с идентификаторами от 1 до 10. Теперь я хотел бы сделать это:
p = Pirate.first
p.name = nil
p.parrot_ids = [1,2,3]
p.save if p.valid?
Поскольку объект pirate недействителен (отсутствует имя), я не хочу, чтобы он был сохранен. Тем не менее, попугаи теперь связаны с пиратом и передаются в базе данных.
Как я могу назначить попугаев, но есть ли ссылки на попугаев, сохраненные только в базе данных, когда p.save успешно? I.e., как я могу сохранить пират и ссылки на попугаев в одной транзакции?
Ответы
Ответ 1
Вероятно, вам стоит взглянуть на транзакции Active Record.
Вы можете обернуть свой код, как он есть, в блок транзакций. Транзакции являются защитными блоками, где заявления SQL являются только постоянными, если все они могут выполняться как одно атомное действие
http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
Ответ 2
Вы можете немного изменить свои операции:
p = Pirate.first
p.name = nil
if p.save
p.parrot_ids = [1,2,3]
end
Обратите внимание, что нет необходимости в "if p.valid?" после p.save; потому что действительны? вызывается с помощью сохранения, определяя, была ли сделана попытка записать данные в базу данных.
Если ваши попугаи ранее не существовали, вы можете использовать p.parrots.build(attributes = {...}) для создания новых попугаев, которые не будут сохранены до сохранения родителя-пирата.
См. раздел "Несвязанные объекты и ассоциации" в ActiveRecord:: Ассоциации: Документация ClassMethods.
Ответ 3
К сожалению, Rails очень похож на существующие объекты; если объект уже существует и вы futz со своими ассоциациями, обновление всегда запускается. Если у вас есть возможность использовать опцию .build
, как упоминает KenB, то это один из способов обойти проблему. Однако, за исключением этого, я могу думать только об одном способе справиться с этим на данный момент; завершите всю операцию в транзакции, например:
Pirate.transaction do
p = Pirate.first
p.name = nil
p.parrot_ids = [1,2,3]
if !p.save # Performing save in this manner will return false if validations fail (ie same as your p.valid?)
raise ActiveRecord::Rollback # should rollback anything executing within this transaction block
end
end
Сообщите мне, если это поможет, и извините, если это не так.