问题
I am running ActiveRecord 3.2.6. Given I have these model definitions:
My Invoice model
class Invoice < ActiveRecord::Base
has_many :items, :autosave => true, :dependent => :delete_all
attr_accessible :recipient_email
# This is just a simple wrapper with allows me to build multiple
# items at once and to specify them as a Hash instead of Item.new.
def items=(ary)
super ary.map{|item| item.is_a?(Hash) ? items.build(item) : item}
end
end
My Item model
class Item < ActiveRecord::Base
belongs_to :invoice
attr_accessible :amount, :description, :invoice_id, :value
end
My goal is to save the invoice items directly in the model. This works without problems, when the Invoice is freshly created. One call to Invoice#save! and everything is saved.
> i = Invoice.new(:recipient_email => "foobar@example.org")
> i.items = [{amount: 10, description: "Bottles of Milk", value: 0.40},
{amount: 1, description: "Shipping fee to Antarctica", value: 217.38}]
> i.save!
SQL (23.5ms) INSERT INTO "invoices" [...]
SQL (0.3ms) INSERT INTO "items" [...]
SQL (0.2ms) INSERT INTO "items" [...]
=> true
However, when I try to update the items in an Invoice that already exists, it deletes the old items before I save the new items.
# Load invoice ID 1, with two items: ID 1 and ID 2.
> i = Invoice.find(1)
# It deletes the old items here
> i.items = [{amount: 10, description: "Less buggy objective relational mappers", value: 1337.00}]
SQL (0.8ms) DELETE FROM items WHERE id IN (1, 2)
# But it should delete the new items here, before inserting the new items,
# wrapping everything in a transaction.
> i.save!
SQL (1.0ms) INSERT INTO "items" [...]
(192.6ms) commit transaction
How can I tell ActiveRecord to delete the old items only when Invoice#save! is called? Or is this a bug in ActiveRecord?
EDIT: My question - clarified
I do not want the DELETE queries to run when the items are assigned (i.items = ...), but when the invoice containing the items is saved (invoice.save!). It should mark the old items for deletions and the new items for insertation and then execute the query on invoice.save!. Is this possible with ActiveRecord?
EDIT 2: Further clarification
As some do not get the question right, some further clarifications. I must admit, this is pretty complex. So here is the difference between what actually happens and what I want to happen.
What I want
This does not happen. I want it to happen. This is completely fictitious. Compare it to the listing above to see the difference.
# (1) Load invoice ID 1, with two items: ID 1 and ID 2.
> i = Invoice.find(1)
# (2) Assign new items, delete the old ones. New stuff exists in memory, not in database
> i.items = [{amount: 10, description: "Less buggy objective relational mappers", value: 1337.00}]
# (3) Save everything to database. Run queries.
> i.save!
(0.0ms) begin transactions
SQL (0.8ms) DELETE FROM items WHERE id IN (1, 2)
SQL (1.0ms) INSERT INTO "items" [...]
(192.6ms) commit transaction
What actually hapens
The DELETE query runs on at point (2). But it should run at point (3). (Compare to above listing).
回答1:
It will delete the old items because you configured association as autosave
has_many :items, :autosave => true, :dependent => :delete_all
Remove autosave and try again, it will work.
回答2:
Since you want append action in assignment, I believe this should work:
def items=(ary)
super(ary.map{|item| item.is_a?(Hash) ? items.build(item) : item} + self.items)
end
来源:https://stackoverflow.com/questions/11353582/delete-associated-records-when-model-is-saved