How to add existing records to has_many without it saving to the DB immediately?
Suppose I've got this:
class Pirate < ActiveRecord::Base
has_many :parrots
validates_presence_of :name
end
class Parrot < ActiveRecord::Base
belongs_to :pirate
end
And I've got existing开发者_如何学Go Pirates and Parrots with ids 1 to 10. Now I'd like to do this:
p = Pirate.first
p.name = nil
p.parrot_ids = [1,2,3]
p.save if p.valid?
Because the pirate object is not valid (it's missing a name) I don't want it to be saved. However, the parrots are linked to the pirate now, and it's committed in the database.
How can I assign the parrots, but have the links to the parrots only saved to the database when p.save is successful? I.e., how can I save the pirate and the links to the parrots in one transaction?
You should probably take a look at the Active Record transactions. You can wrap your code as you have it in a transaction block. Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action
http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
You could rearrange your operations a bit:
p = Pirate.first
p.name = nil
if p.save
p.parrot_ids = [1,2,3]
end
Note there's no need for "if p.valid?" after p.save; because valid? is invoked by save, determining whether an attempt is made to write the data to the database.
If your parrots were not pre-existing, you could use p.parrots.build(attributes={...}) to create new parrots that won't be saved until the pirate parent is saved.
See the section on Unsaved Objects and Associations in the ActiveRecord::Associations::ClassMethods documentation.
Unfortunately, Rails is very much built this way for existing objects; if the object already exists and you futz with its associations, an update is always triggered. If you have the ability to use the .build
option as KenB mentioned, then that is one way to get around the issue. However, barring that, I can only think of one way to handle this at the moment; wrap the entire operation in a transaction, like so:
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
Let me know if this helps, and apologies if it doesn't.
精彩评论