【问题标题】:Delete associated records when model is saved保存模型时删除关联记录
【发布时间】:2012-07-05 22:51:22
【问题描述】:

我正在运行 ActiveRecord 3.2.6。鉴于我有这些模型定义:

我的发票模型

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

我的项目模型

class Item < ActiveRecord::Base
  belongs_to :invoice

  attr_accessible :amount, :description, :invoice_id, :value
end

我的目标是将发票项目直接保存在模型中。当新创建发票时,这没有问题。拨打Invoice#save! 即可保存所有内容。

> 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 

但是,当我尝试更新 Invoice 中已经存在的项目时,它会在我保存新项目之前删除旧项目。

# 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

如何告诉 ActiveRecord 仅在调用 Invoice#save! 时删除旧项目?或者这是 ActiveRecord 中的错误?

编辑:我的问题 - 澄清

我不希望在分配项目 (i.items = ...) 时运行 DELETE 查询,但在保存包含项目的发票时 (invoice.save!)。它应该将旧项目标记为删除,将新项目标记为插入,然后在invoice.save! 上执行查询。 ActiveRecord 可以做到这一点吗?

编辑 2:进一步澄清

由于有些人没有正确回答问题,因此需要进一步澄清。我必须承认,这很复杂。所以这就是实际发生的事情和我想要发生的事情之间的区别。

我想要什么

不会发生。我希望它发生。这完全是虚构的。将其与上面的清单进行比较,看看有什么不同。

# (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

实际发生了什么

DELETE 查询在点 (2) 处运行。但它应该在(3) 点运行。 (与上面的清单相比)。

【问题讨论】:

  • 我完全可以同情这一点。来自休眠状态,我希望事情能以这种方式工作。某种关联属性表示“在 parent.save 上,将孩子的数据库状态与 parent.children 同步”。然而,这很难支持:它需要非常仔细地跟踪对关联的修改以及通过关联来了解在保存时应该对孩子做什么。

标签: ruby-on-rails ruby-on-rails-3 activerecord


【解决方案1】:

它将删除旧项目,因为您将关联配置为自动保存

has_many :items, :autosave => true, :dependent => :delete_all

删除自动保存并重试,它会工作。

【讨论】:

  • 不,没有效果。你自己试过吗?顺便说一句:我想要它删除项目。但不是在关联项目时 (invoice.items = ...),而是在保存发票时 (invoice.save!)
【解决方案2】:

由于您想在作业中附加操作,我相信这应该可行:

  def items=(ary)
    super(ary.map{|item| item.is_a?(Hash) ? items.build(item) : item} + self.items)
  end

【讨论】:

  • 恐怕你没有理解我的问题。我希望删除旧项目。但我希望在调用 save! 时删除它们,而不是在调用 items = ... 时删除它们。给我一点时间来编辑我的问题并进一步澄清......
【解决方案3】:

您可以使用autosave: true 选项配置您的关联,并使用mark_for_destruction 指定您要在save 上销毁的每个项目。

考虑到您的第二次编辑,您可以执行以下操作:

# (1) Load invoice ID 1, with two items: ID 1 and ID 2.
> i = Invoice.find(1)

# (2) Mark the old items to be destroyed on save. They are still in memory and in the database for now.
> i.items.each(&:mark_for_destruction)

# (3) Assign new items. New stuff exists in memory, not in database.
> i.items.build([{amount: 10, description: "Less buggy objective relational mappers", value: 1337.00}])

# (4) Delete the items which has been mark for destruction. 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多