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から説明した方がわかりやすいよねえ。