用于管理交易最佳实践的RoR 4.x方法

时间:2015-06-25 10:07:39

标签: ruby-on-rails ruby ruby-on-rails-4 transactions

首先 - 抱歉也许是愚蠢的问题,但我是RoR世界的初学者,我需要一些关于数据库事务如何在模型/控制器中工作的基本清晰度。我来自Java世界,其中某些事情以某种方式完成,当使用RoR时,我自然地将它与Java Web框架进行比较,因此如果看起来完全不同,我会感到困惑: - )

在我的一个控制器操作中,我需要修改并保存多个模型,让我们说:订单,发票,付款。

据我所知,标准"保存"每个模型上的方法在它自己的事务中执行,因此如果我只是写:

payment.save
order.save
invoice.save

这将创建3个独立的数据库事务,每个模型将保存在它自己的事务中 - 这不是我想要的,因为我想确保这些模型中的全部或全部都不是保存。

我找到了这篇文章:http://markdaggett.com/blog/2011/12/01/transactions-in-rails/,它演示了如何包装多个" save"进入单一交易。它已经相当陈旧但我希望它仍然有效(如果我错了,请纠正我。)

我不喜欢的一件事是我需要在我需要的每个控制器操作中明确地管理这些事务。我希望在幕后发生这种情况"喜欢"在视野中打开会话"从Java世界中已知的模式,在执行任何数据库查询之前在过滤器中启动数据库事务,并在所有控制器操作完成后在过滤器中提交事务。

我正在考虑在我的RoR应用程序中使用类似的方法,我发现博客文章演示了如何做到这一点:http://blog.endpoint.com/2011/10/rails-controllers-and-transactions.html但是我不确定这是否是"最佳实践"因为另一篇文章(http://markdaggett.com/blog/2011/12/01/transactions-in-rails/)说"在控制器中使用交易是常见的反模式,以避免" (虽然不知道为什么)。

有人能指导我进入"对"接近?

谢谢,

米甲

2 个答案:

答案 0 :(得分:2)

基本交易可以这样做:

ActiveRecord::Base.transaction do
  payment.save!
  order.save!
  invoice.save!
end

然而,您想要的是一个服务对象,它将负责处理它。


class PaymentHadler
  def initialize(payment, order, invoice)
  end
  def perform
  end
end

根据您的应用程序,向PaymentHandler提供参数哈希并在处理程序中实例化模型可能是明智的。在大多数情况下,它们需要关联,因此在控制器内部构建。您的里程可能会有所不同:)

答案 1 :(得分:1)

交易是模型层关注的问题。我不认为他们属于控制器,虽然它肯定会起作用。这不是一个很难的规则。

需要考虑的一件事是这个代码是否需要在控制器的上下文之外运行?例如在后台工作?还是一个佣金任务?在这种情况下,将逻辑合并到模型层将使重用更容易。

要在模型层中完成此操作,“Rails方式”是使用回调。 *_save/create/update回调在当前数据库事务中自动执行,这意味着您可以“免费”获得所需的事务行为,而无需显式编写事务代码。

如果您的模型具有明确的父子关系,那么您可以在“顶级”模型上注册回调,例如:

class Order < ActiveRecord::Base
  has_one :invoice
  has_one :payment

  after_create :save_child_records

  private

  def save_child_records
    invoice.save!
    payment.save!
  end
end

order.save
# triggers invoice.save! and payment.save! 
# all in one transaction

或者,您可以创建一个不受数据库表支持的全新模型,该模型仅用于编排多个模型。这有时称为“服务对象”或“操作对象”。

为此,我推荐使用active_type gem,它允许您使用所有标准的ActiveRecord回调,从而提供相同的理想事务行为:

class ProcessPayment < ActiveType::Object
  attr_accessor :order, :invoice, :payment

  # You can declare ActiveRecord validations here too
  # Or register other before_save callbacks for business logic

  before_save :save_records

  private

  def save_records
    order.save!
    invoice.save!
    payment.save!
  end
end

process_payment.save
# order, invoice, payment are all saved in a single transaction