Как добавить существующие записи в 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

Сообщите мне, если это поможет, и извините, если это не так.