返回内部事务和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

这里的想法是为呼叫者提供这样的流程:

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

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

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

发件人http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

一个异常是ActiveRecord::Rollback异常,它将引发时触发ROLLBACK,但不会被事务块。

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

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

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

返回错误对象没有任何作用。这只是另一个被传递的物体。

我相信这在Rails 7中已经改变了。在编写时,最新版本的Rails(7.0.2)将在遇到return时静默地回滚事务。

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

TLDR;如果需要从当前上下文返回,请不要使用return使用next

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

最新更新