@m_seki の

I like ruby tooから引っ越し

ネストしたトランザクションに対する作戦

Koyaではネストしたトランザクションの部分的なcommitは扱えないことにしました。ただし、トランザクションをネストすることは可能です。その場合、例外がどれかのトランザクションのブロックの外(?)まで到達すると、その一番外側のトランザクション全体がcommitされなくなります。「これを一つの塊としたい」処理をtransaction { ... }で囲めば、ブロックの処理全体がcommitされるか、あるいは、全くされないかのいずれかになるので、そういう処理は明示的にtransaction { ... }しといてね。

二回に分けてカウンタを進める意味のないクラスを例にします。up2()に偽を与えると例外が発生するようになってます。ま、サンプルだからね。

require 'koya'

class DoubleUp < Koya::KoyaObject
  def initialize
    @value = 0
  end
  attr_reader :value

  def up2(succ=true)
    transaction do
      up2_wo_txn(succ)
    end
  end

  def up2_wo_txn(succ=true)
    @value += 1
    raise 'Failed' unless succ
    @value += 1
  end
end

s = Koya::Store.new('test.db')
count = s.transaction { s.root['up'] = DoubleUp.new }

永続オブジェクトの生成はいずれかのトランザクションで行います。countにDoubleUpのインスタンスへの参照が入ってます。

p count.value # => 0
count.up2
p count.value # => 2

countのメソッド呼び出しをすると、その単位のトランザクションがこっそり用意されるので明示的に用意する必要はありません。

次に、up2を失敗させてみます。3にならず、2になります。

begin
  count.up2(false)
rescue
end
p count.value #=> 2

up2_wo_txnを直接呼んでトランザクションなしで実験します。up2_wo_txnが発生する例外をrescueして、外側のトランザクションに届かないようにするとどうなるでしょう。

s.transaction do
  begin
    count.up2_wo_txn(false)
  rescue
  end
end
p count.value # => 3

最初の@value += 1が処理されたあと、例外がトランザクションまで到達しないのでcommitされちゃいました。
up2を使って内側のトランザクションが効くかどうか実験します。

begin
  s.transaction do
    count.up2
    p count.value # => 5
    begin
      count.up2(false)
    rescue
    end
  end
rescue
  p $!.class # => Koya::TransactionAborted
end

p count.value # => 3   (巻き戻る)

内側のトランザクションが失敗したのに、それが外側に到達しなかった時、次のデータベースへの操作、あるいは外側のトランザクションの終了処理でKoya::TransactionAborted例外が発生します。(←まだ迷ってる仕様)

最初のup2によって、count.valueは5となりますが、二つ目のup2が失敗するのでトランザクション全体が失敗して、count.valueは3のままです。up2(false)の一部がcommitされることもありません。

って、up2_wo_txnから説明した方がわかりやすいよねえ。