3

我一直在阅读 ActiveRecord 事务会自动包裹保存和销毁操作。我的问题与以下情况有关。

我有一个库存系统,用于跟踪货件并在创建货件时调整产品的库存量。当我删除一个货件时,我已经将货件数量重新添加到产品的库存量中。这种情况是为用户弄乱货物而设计的。删除货件会将已发货的物品重新添加到库存中。

我的问题是当 product_shipments 循环通过时,是否有必要提供 Product.transaction 块,或者我可以省略这个,因为 destroy 方法自动包装在事务中?可以像我一样将整个循环包装在事务中吗?如果出现问题,我怎样才能最好地确保所有这些数据库操作都回滚?

def destroy
  @shipment = Shipment.find(params[:id])
  @shipments = @shipment.product_shipments
  Product.transaction do
    @shipments.each do |s|
      @difference = -(s.qty_shipped.to_i)
      Product.update_inventory_quantities(@difference, s.product_id)
    end
  end
  @shipment.destroy
  respond_with @shipment, :location => shipments_url
end
4

2 回答 2

2

为了详细说明 Mischa 的建议,ActiveRecord 回调将允许您从几个重要方面改进此代码。首先,它删除了提示你写这个问题的丑陋交易块。几乎总是,如果你在 Rails 中看到这样的事务块,你可能做错了什么。其次,它开始将事情转移到他们关注的地方。这不仅会使维护变得更容易,因为它们位于直观的位置并且很大程度上利用了内置的 Rails 方法,而且它还会使测试变得更加容易。

我怀疑这个应用程序没有测试。这可能是写几个的好借口,因为你正在做这个调整。它开始分解到一些简单的规范可能会大有帮助的地步,而您的应用程序复杂性看起来可能已经到了可以从增加的头脑中受益的地步(尤其是由于涉及到金钱)。

我将对您的对象的关系做出某些假设,并将它们排除在下面的示例代码之外,但我正在想象一个 has_many :through 类型的情况,涉及 Shipments、Products 和 ProductShipments。

您的控制器已清理:

def destroy
  # note: you may not need the additional scope of @shipment - depending on your view, you may be able to omit the @
  @shipment = Shipment.destroy(params[:id])
  respond_with @shipment, :location => shipments_url
end

您的发货型号:

class Shipment < ActiveRecord::Base

  before_destroy :restore_unshipped_inventory

  def restore_unshipped_inventory
    product_shipments.each(&:restore_unshipped_inventory)
  end

end

您的加盟模式:

class ProductShipment < ActiveRecord::Base  

  def restore_unshipped_inventory
    difference = -(s.qty_shipped.to_i)
    Product.update_inventory_quantities(difference, product.id)
  end

end

老实说,我什至会更进一步,让 ProductShipment 中的 restore_unshipped_inventory 对 product ( product.update_inventory_quantities) 的实例进行操作,即使该实例方法所做的只是调用您在那里的类方法,只是为了进一步隔离不相关的逻辑。

于 2012-10-09T05:42:09.037 回答
0

你所做的是对的。但您需要在交易中添加@shipment.destroy。因此,如果交易失败,您的货物将不会被销毁。如果您的事务由于某种原因失败,则不会触发任何提交并且您的事务将回滚。

于 2012-10-09T03:59:52.853 回答