返回内部事务和 ActiveRecord::Rollback

ran*_*guy 3 ruby transactions ruby-on-rails control-flow rails-activerecord

create是一种方法,只有当一切都按预期进行时才应该返回 true,否则返回 false。我要使用错误代码样式的控制流。

class TransferOperator
  class TransferError < Struct.new(:type, :message); ; end

  attr_reader :transfer, :error

  def initialize(transfer)
    @transfer = transfer
  end

  # Creates the transfer and locks money in the bank
  def create
    return error(:validation_error) if transfer.invalid?

    to_bank = transfer.main_to_bank
    to_bank.with_lock do
      # How does return here behave? Should a raise be issued instead and caught outside?
      return error(:insufficient_buffer) if to_bank.available_balance < transfer.amount
      to_bank.available_balance -= transfer.amount
      to_bank.locked_balance += transfer.amount
      to_bank.save!
      transfer.save!
    end

    # Is it guaranteed here that the above transaction has always been succesful?
    true
  end

  private

  def error(type, message='')
    @error = TransferError.new(type, message)
    false
  end
end
Run Code Online (Sandbox Code Playgroud)

这里的想法是为调用者提供这样的流程:

def move_money
  @transfer = Transfer.new(params)
  operator = TransferOperator.new(@transfer)
  if operator.create
    redirect_to :root, notice: 'success!'
  else
    if operator.error.type == :validation_error
      render action: 'new'
    elsif operator.error.type == :insufficient_buffer
      redirect_to :root, notice: 'not enough money'
    else
      # Handle other errors here...
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

事务中的错误返回会发生什么?

如果返回true,是否保证交易成功?

来自http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

一个例外是 ActiveRecord::Rollback 异常,它在引发时会触发 ROLLBACK,但不会被事务块重新引发。

Rails 有可能自己提出ActiveRecord::Rollback吗?如果是这样,那么事务将默默地失败并返回 true(这不是我们想要的)。

dan*_*nts 8

我相信这在 Rails 7 中已经改变。在撰写本文时,最新版本的 Rails (7.0.2) 将在遇到return.

请参阅https://github.com/rails/rails/issues/45017和早期版本中的弃用警告https://github.com/rails/rails/pull/29333

太长了;如果您需要从当前上下文返回,请不要使用returnuse 。next

如果您嵌套在多个块​​内并且无法使用next,请将事务的内容提取到其自己的方法中并在那里使用 return 。


inf*_*sed 6

如果要使事务回滚,则必须引发错误。你有几个选择:

  1. RaiseActiveRecord::Rollback并且事务将被回滚并且不会在事务块之外重新引发错误。正如您所说,这将默默地回滚事务。可能不是你想要的。
  2. 引发任何其他类型的错误。这将导致事务回滚并引发您的错误。您可以挽救该错误以适当地重定向用户。

返回一个错误对象什么都不做。这只是另一个被传递的对象。

  • Rails 本身永远不会引发“ActiveRecord::Rollback”。好吧,从技术上讲,Rails 在内部使用此错误,但它不会在您的事务块中引发它。 (2认同)