如何从ActiveRecord中的事务中排除模型?

时间:2010-09-10 15:25:11

标签: ruby-on-rails activerecord transactions

我有一个特殊情况模型,它不能成为外部交易的一部分:

Outer.Transaction do
  ...

  Inner.create(:blah)

  ...
end

我如何阻止Inner成为交易的一部分,假设Inner不知道它将被引入哪个特定交易?

例如,创建内部事务是不行的,因为它也将成为外部事务的一部分。

我想这样做是因为内部模型需要立即写入而不是等待外部事务提交。

2 个答案:

答案 0 :(得分:1)

我很想知道这需要什么构造!

我认为如你所描述的那样,如果没有一点ha,就会很难做到这一点。例如,您可以在mysql中将Inner的表存储类型设置为不支持事务的表(MyIsam eg),同时保持其他类的表存储具有支持事务的内容(YUK!)。

如果可以,在交易完成之前,你几乎肯定会更好地推迟Inner.create。您可以使用begin with ensure确保始终发生创建。类似的东西:

create_inner = false
begin
  Outer.transaction.do
    ...
    create_inner = true # instead of Inner.create(:blah)
    ...
  end
ensure
  if create_inner
    Inner.create(:blah)
  end
end

如果块的其余部分依赖于创建的Inner实例,则会变得更加复杂。您可以在块中创建实例,并在块的末尾将created_inner设置为false,这样,如果代码无异常地运行,它将在事务中创建,并且您不会再在确保。

如果你想在一般情况下这样做,你可以在Inner上定义一个类方法来执行一个块但总是创建一个Inner对象。您还需要向after_create添加Inner。当事务成功时,您将依赖块中的Inner.create调用来创建它,但如果它被回滚,那么您需要在之后创建它。例如:

class Inner < ActiveRecord::Base

  def self.ensure_created(&block)
    Thread.current[:created_inner] = false        
    begin
      block.call
    rescue => e
      if Thread.current[:created_inner]
        Inner.create(:blah)
      end
      raise e
    end
  end

  def after_create
    # Flag that an instance has been created in this thread so
    # that if we rollback out of a transaction we can create again
    Thread.current[:created_inner] = true
  end

然后你会这样称呼:

Inner.ensure_created do
  Outer.transaction do
    ...
    Inner.create(:blah)
    ...
  end
end

然而,这种方法有很多缺点,我不确定我会提倡它。这很复杂。如果引发ActiveRecord :: Rollback,它将无效,因为该异常不会从Outer.transaction冒出,但会导致不创建Inner实例。当嵌套两个或多个调用时,它将无法正常工作。最后我还没有彻底测试过 - 请谨慎使用!

答案 1 :(得分:0)

您可以为Inner定义单独的数据库连接,然后该事务将仅应用于Outers连接。