开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜