From 6e283e9dc747e01444b9f17e6aab5f9240d5b5d7 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 15 Mar 2006 18:22:12 +0000 Subject: Upgrade to Transaction::Simple 1.3 git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3875 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/lib/active_record/vendor/simple.rb | 1395 +++++++++++------------ 1 file changed, 693 insertions(+), 702 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/vendor/simple.rb b/activerecord/lib/active_record/vendor/simple.rb index 1bd332c882..7ac3cd08ec 100644 --- a/activerecord/lib/active_record/vendor/simple.rb +++ b/activerecord/lib/active_record/vendor/simple.rb @@ -1,702 +1,693 @@ -# :title: Transaction::Simple -# -# == Licence -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -# IN THE SOFTWARE. -#-- -# Transaction::Simple -# Simple object transaction support for Ruby -# Version 1.11 -# -# Copyright (c) 2003 Austin Ziegler -# -# $Id: simple.rb,v 1.2 2004/08/20 13:56:37 webster132 Exp $ -# -# ========================================================================== -# Revision History :: -# YYYY.MM.DD Change ID Developer -# Description -# -------------------------------------------------------------------------- -# 2003.07.29 Austin Ziegler -# Added debugging capabilities and VERSION string. -# 2003.08.21 Austin Ziegler -# Added named transactions. -# -# ========================================================================== -#++ -require 'thread' - - # The "Transaction" namespace can be used for additional transactional - # support objects and modules. -module Transaction - - # A standard exception for transactional errors. - class TransactionError < StandardError; end - # A standard exception for transactional errors involving the acquisition - # of locks for Transaction::Simple::ThreadSafe. - class TransactionThreadError < StandardError; end - - # = Transaction::Simple for Ruby - # Simple object transaction support for Ruby - # - # == Introduction - # - # Transaction::Simple provides a generic way to add active transactional - # support to objects. The transaction methods added by this module will - # work with most objects, excluding those that cannot be Marshaled - # (bindings, procedure objects, IO instances, or singleton objects). - # - # The transactions supported by Transaction::Simple are not backed - # transactions; that is, they have nothing to do with any sort of data - # store. They are "live" transactions occurring in memory and in the - # object itself. This is to allow "test" changes to be made to an object - # before making the changes permanent. - # - # Transaction::Simple can handle an "infinite" number of transactional - # levels (limited only by memory). If I open two transactions, commit the - # first, but abort the second, the object will revert to the original - # version. - # - # Transaction::Simple supports "named" transactions, so that multiple - # levels of transactions can be committed, aborted, or rewound by - # referring to the appropriate name of the transaction. Names may be any - # object *except* +nil+. - # - # Copyright:: Copyright © 2003 by Austin Ziegler - # Version:: 1.1 - # Licence:: MIT-Style - # - # Thanks to David Black for help with the initial concept that led to this - # library. - # - # == Usage - # include 'transaction/simple' - # - # v = "Hello, you." # => "Hello, you." - # v.extend(Transaction::Simple) # => "Hello, you." - # - # v.start_transaction # => ... (a Marshal string) - # v.transaction_open? # => true - # v.gsub!(/you/, "world") # => "Hello, world." - # - # v.rewind_transaction # => "Hello, you." - # v.transaction_open? # => true - # - # v.gsub!(/you/, "HAL") # => "Hello, HAL." - # v.abort_transaction # => "Hello, you." - # v.transaction_open? # => false - # - # v.start_transaction # => ... (a Marshal string) - # v.start_transaction # => ... (a Marshal string) - # - # v.transaction_open? # => true - # v.gsub!(/you/, "HAL") # => "Hello, HAL." - # - # v.commit_transaction # => "Hello, HAL." - # v.transaction_open? # => true - # v.abort_transaction # => "Hello, you." - # v.transaction_open? # => false - # - # == Named Transaction Usage - # v = "Hello, you." # => "Hello, you." - # v.extend(Transaction::Simple) # => "Hello, you." - # - # v.start_transaction(:first) # => ... (a Marshal string) - # v.transaction_open? # => true - # v.transaction_open?(:first) # => true - # v.transaction_open?(:second) # => false - # v.gsub!(/you/, "world") # => "Hello, world." - # - # v.start_transaction(:second) # => ... (a Marshal string) - # v.gsub!(/world/, "HAL") # => "Hello, HAL." - # v.rewind_transaction(:first) # => "Hello, you." - # v.transaction_open? # => true - # v.transaction_open?(:first) # => true - # v.transaction_open?(:second) # => false - # - # v.gsub!(/you/, "world") # => "Hello, world." - # v.start_transaction(:second) # => ... (a Marshal string) - # v.gsub!(/world/, "HAL") # => "Hello, HAL." - # v.transaction_name # => :second - # v.abort_transaction(:first) # => "Hello, you." - # v.transaction_open? # => false - # - # v.start_transaction(:first) # => ... (a Marshal string) - # v.gsub!(/you/, "world") # => "Hello, world." - # v.start_transaction(:second) # => ... (a Marshal string) - # v.gsub!(/world/, "HAL") # => "Hello, HAL." - # - # v.commit_transaction(:first) # => "Hello, HAL." - # v.transaction_open? # => false - # - # == Contraindications - # - # While Transaction::Simple is very useful, it has some severe limitations - # that must be understood. Transaction::Simple: - # - # * uses Marshal. Thus, any object which cannot be Marshaled cannot - # use Transaction::Simple. - # * does not manage resources. Resources external to the object and its - # instance variables are not managed at all. However, all instance - # variables and objects "belonging" to those instance variables are - # managed. If there are object reference counts to be handled, - # Transaction::Simple will probably cause problems. - # * is not inherently thread-safe. In the ACID ("atomic, consistent, - # isolated, durable") test, Transaction::Simple provides CD, but it is - # up to the user of Transaction::Simple to provide isolation and - # atomicity. Transactions should be considered "critical sections" in - # multi-threaded applications. If thread safety and atomicity is - # absolutely required, use Transaction::Simple::ThreadSafe, which uses a - # Mutex object to synchronize the accesses on the object during the - # transactional operations. - # * does not necessarily maintain Object#__id__ values on rewind or abort. - # This may change for future versions that will be Ruby 1.8 or better - # *only*. Certain objects that support #replace will maintain - # Object#__id__. - # * Can be a memory hog if you use many levels of transactions on many - # objects. - # - module Simple - VERSION = '1.1.1.0'; - - # Sets the Transaction::Simple debug object. It must respond to #<<. - # Sets the transaction debug object. Debugging will be performed - # automatically if there's a debug object. The generic transaction error - # class. - def self.debug_io=(io) - raise TransactionError, "Transaction Error: the transaction debug object must respond to #<<" unless io.respond_to?(:<<) - @tdi = io - end - - # Returns the Transaction::Simple debug object. It must respond to #<<. - def self.debug_io - @tdi - end - - # If +name+ is +nil+ (default), then returns +true+ if there is - # currently a transaction open. - # - # If +name+ is specified, then returns +true+ if there is currently a - # transaction that responds to +name+ open. - def transaction_open?(name = nil) - if name.nil? - Transaction::Simple.debug_io << "Transaction [#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" unless Transaction::Simple.debug_io.nil? - return (not @__transaction_checkpoint__.nil?) - else - Transaction::Simple.debug_io << "Transaction(#{name.inspect}) [#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" unless Transaction::Simple.debug_io.nil? - return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name)) - end - end - - # Returns the current name of the transaction. Transactions not - # explicitly named are named +nil+. - def transaction_name - raise TransactionError, "Transaction Error: No transaction open." if @__transaction_checkpoint__.nil? - Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Transaction Name: #{@__transaction_names__[-1].inspect}\n" unless Transaction::Simple.debug_io.nil? - @__transaction_names__[-1] - end - - # Starts a transaction. Stores the current object state. If a - # transaction name is specified, the transaction will be named. - # Transaction names must be unique. Transaction names of +nil+ will be - # treated as unnamed transactions. - def start_transaction(name = nil) - @__transaction_level__ ||= 0 - @__transaction_names__ ||= [] - - if name.nil? - @__transaction_names__ << nil - s = "" - else - raise TransactionError, "Transaction Error: Named transactions must be unique." if @__transaction_names__.include?(name) - @__transaction_names__ << name - s = "(#{name.inspect})" - end - - @__transaction_level__ += 1 - - Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} Start Transaction#{s}\n" unless Transaction::Simple.debug_io.nil? - - @__transaction_checkpoint__ = Marshal.dump(self) - end - - # Rewinds the transaction. If +name+ is specified, then the intervening - # transactions will be aborted and the named transaction will be - # rewound. Otherwise, only the current transaction is rewound. - def rewind_transaction(name = nil) - raise TransactionError, "Transaction Error: Cannot rewind. There is no current transaction." if @__transaction_checkpoint__.nil? - if name.nil? - __rewind_this_transaction - s = "" - else - raise TransactionError, "Transaction Error: Cannot rewind to transaction #{name.inspect} because it does not exist." unless @__transaction_names__.include?(name) - s = "(#{name})" - - while @__transaction_names__[-1] != name - @__transaction_checkpoint__ = __rewind_this_transaction - Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Rewind Transaction#{s}\n" unless Transaction::Simple.debug_io.nil? - @__transaction_level__ -= 1 - @__transaction_names__.pop - end - __rewind_this_transaction - end - Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Rewind Transaction#{s}\n" unless Transaction::Simple.debug_io.nil? - self - end - - # Aborts the transaction. Resets the object state to what it was before - # the transaction was started and closes the transaction. If +name+ is - # specified, then the intervening transactions and the named transaction - # will be aborted. Otherwise, only the current transaction is aborted. - def abort_transaction(name = nil) - raise TransactionError, "Transaction Error: Cannot abort. There is no current transaction." if @__transaction_checkpoint__.nil? - if name.nil? - __abort_transaction(name) - else - raise TransactionError, "Transaction Error: Cannot abort nonexistant transaction #{name.inspect}." unless @__transaction_names__.include?(name) - - __abort_transaction(name) while @__transaction_names__.include?(name) - end - self - end - - # If +name+ is +nil+ (default), the current transaction level is closed - # out and the changes are committed. - # - # If +name+ is specified and +name+ is in the list of named - # transactions, then all transactions are closed and committed until the - # named transaction is reached. - def commit_transaction(name = nil) - raise TransactionError, "Transaction Error: Cannot commit. There is no current transaction." if @__transaction_checkpoint__.nil? - - if name.nil? - s = "" - __commit_transaction - Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{s}\n" unless Transaction::Simple.debug_io.nil? - else - raise TransactionError, "Transaction Error: Cannot commit nonexistant transaction #{name.inspect}." unless @__transaction_names__.include?(name) - s = "(#{name})" - - while @__transaction_names__[-1] != name - Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{s}\n" unless Transaction::Simple.debug_io.nil? - __commit_transaction - end - Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{s}\n" unless Transaction::Simple.debug_io.nil? - __commit_transaction - end - self - end - - # Alternative method for calling the transaction methods. An optional - # name can be specified for named transaction support. - # - # #transaction(:start):: #start_transaction - # #transaction(:rewind):: #rewind_transaction - # #transaction(:abort):: #abort_transaction - # #transaction(:commit):: #commit_transaction - # #transaction(:name):: #transaction_name - # #transaction:: #transaction_open? - def transaction(action = nil, name = nil) - case action - when :start - start_transaction(name) - when :rewind - rewind_transaction(name) - when :abort - abort_transaction(name) - when :commit - commit_transaction(name) - when :name - transaction_name - when nil - transaction_open?(name) - end - end - - def __abort_transaction(name = nil) #:nodoc: - @__transaction_checkpoint__ = __rewind_this_transaction - - if name.nil? - s = "" - else - s = "(#{name.inspect})" - end - - Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Abort Transaction#{s}\n" unless Transaction::Simple.debug_io.nil? - @__transaction_level__ -= 1 - @__transaction_names__.pop - if @__transaction_level__ < 1 - @__transaction_level__ = 0 - @__transaction_names__ = [] - end - end - - TRANSACTION_CHECKPOINT = "@__transaction_checkpoint__" #:nodoc: - SKIP_TRANSACTION_VARS = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc: - - def __rewind_this_transaction #:nodoc: - r = Marshal.restore(@__transaction_checkpoint__) - - begin - self.replace(r) if respond_to?(:replace) - rescue - nil - end - - r.instance_variables.each do |i| - next if SKIP_TRANSACTION_VARS.include?(i) - if respond_to?(:instance_variable_get) - instance_variable_set(i, r.instance_variable_get(i)) - else - instance_eval(%q|#{i} = r.instance_eval("#{i}")|) - end - end - - if respond_to?(:instance_variable_get) - return r.instance_variable_get(TRANSACTION_CHECKPOINT) - else - return r.instance_eval(TRANSACTION_CHECKPOINT) - end - end - - def __commit_transaction #:nodoc: - if respond_to?(:instance_variable_get) - @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_variable_get(TRANSACTION_CHECKPOINT) - else - @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_eval(TRANSACTION_CHECKPOINT) - end - - @__transaction_level__ -= 1 - @__transaction_names__.pop - if @__transaction_level__ < 1 - @__transaction_level__ = 0 - @__transaction_names__ = [] - end - end - - private :__abort_transaction, :__rewind_this_transaction, :__commit_transaction - - # = Transaction::Simple::ThreadSafe - # Thread-safe simple object transaction support for Ruby. - # Transaction::Simple::ThreadSafe is used in the same way as - # Transaction::Simple. Transaction::Simple::ThreadSafe uses a Mutex - # object to ensure atomicity at the cost of performance in threaded - # applications. - # - # Transaction::Simple::ThreadSafe will not wait to obtain a lock; if the - # lock cannot be obtained immediately, a - # Transaction::TransactionThreadError will be raised. - # - # Thanks to Mauricio Fernández for help with getting this part working. - module ThreadSafe - VERSION = '1.1.1.0'; - - include Transaction::Simple - - SKIP_TRANSACTION_VARS = Transaction::Simple::SKIP_TRANSACTION_VARS.dup #:nodoc: - SKIP_TRANSACTION_VARS << "@__transaction_mutex__" - - Transaction::Simple.instance_methods(false) do |meth| - next if meth == "transaction" - arg = "(name = nil)" unless meth == "transaction_name" - module_eval <<-EOS - def #{meth}#{arg} - if (@__transaction_mutex__ ||= Mutex.new).try_lock - result = super - @__transaction_mutex__.unlock - return result - else - raise TransactionThreadError, "Transaction Error: Cannot obtain lock for ##{meth}" - end - ensure - @__transaction_mutex__.unlock - end - EOS - end - end - end -end - -if $0 == __FILE__ - require 'test/unit' - - class Test__Transaction_Simple < Test::Unit::TestCase #:nodoc: - VALUE = "Now is the time for all good men to come to the aid of their country." - - def setup - @value = VALUE.dup - @value.extend(Transaction::Simple) - end - - def test_extended - assert_respond_to(@value, :start_transaction) - end - - def test_started - assert_equal(false, @value.transaction_open?) - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - end - - def test_rewind - assert_equal(false, @value.transaction_open?) - assert_raises(Transaction::TransactionError) { @value.rewind_transaction } - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.gsub!(/men/, 'women') } - assert_not_equal(VALUE, @value) - assert_nothing_raised { @value.rewind_transaction } - assert_equal(true, @value.transaction_open?) - assert_equal(VALUE, @value) - end - - def test_abort - assert_equal(false, @value.transaction_open?) - assert_raises(Transaction::TransactionError) { @value.abort_transaction } - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.gsub!(/men/, 'women') } - assert_not_equal(VALUE, @value) - assert_nothing_raised { @value.abort_transaction } - assert_equal(false, @value.transaction_open?) - assert_equal(VALUE, @value) - end - - def test_commit - assert_equal(false, @value.transaction_open?) - assert_raises(Transaction::TransactionError) { @value.commit_transaction } - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.gsub!(/men/, 'women') } - assert_not_equal(VALUE, @value) - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.commit_transaction } - assert_equal(false, @value.transaction_open?) - assert_not_equal(VALUE, @value) - end - - def test_multilevel - assert_equal(false, @value.transaction_open?) - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.gsub!(/men/, 'women') } - assert_equal(VALUE.gsub(/men/, 'women'), @value) - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.start_transaction } - assert_nothing_raised { @value.gsub!(/country/, 'nation-state') } - assert_nothing_raised { @value.commit_transaction } - assert_equal(VALUE.gsub(/men/, 'women').gsub(/country/, 'nation-state'), @value) - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.abort_transaction } - assert_equal(VALUE, @value) - end - - def test_multilevel_named - assert_equal(false, @value.transaction_open?) - assert_raises(Transaction::TransactionError) { @value.transaction_name } - assert_nothing_raised { @value.start_transaction(:first) } # 1 - assert_raises(Transaction::TransactionError) { @value.start_transaction(:first) } - assert_equal(true, @value.transaction_open?) - assert_equal(true, @value.transaction_open?(:first)) - assert_equal(:first, @value.transaction_name) - assert_nothing_raised { @value.start_transaction } # 2 - assert_not_equal(:first, @value.transaction_name) - assert_equal(nil, @value.transaction_name) - assert_raises(Transaction::TransactionError) { @value.abort_transaction(:second) } - assert_nothing_raised { @value.abort_transaction(:first) } - assert_equal(false, @value.transaction_open?) - assert_nothing_raised do - @value.start_transaction(:first) - @value.gsub!(/men/, 'women') - @value.start_transaction(:second) - @value.gsub!(/women/, 'people') - @value.start_transaction - @value.gsub!(/people/, 'sentients') - end - assert_nothing_raised { @value.abort_transaction(:second) } - assert_equal(true, @value.transaction_open?(:first)) - assert_equal(VALUE.gsub(/men/, 'women'), @value) - assert_nothing_raised do - @value.start_transaction(:second) - @value.gsub!(/women/, 'people') - @value.start_transaction - @value.gsub!(/people/, 'sentients') - end - assert_raises(Transaction::TransactionError) { @value.rewind_transaction(:foo) } - assert_nothing_raised { @value.rewind_transaction(:second) } - assert_equal(VALUE.gsub(/men/, 'women'), @value) - assert_nothing_raised do - @value.gsub!(/women/, 'people') - @value.start_transaction - @value.gsub!(/people/, 'sentients') - end - assert_raises(Transaction::TransactionError) { @value.commit_transaction(:foo) } - assert_nothing_raised { @value.commit_transaction(:first) } - assert_equal(VALUE.gsub(/men/, 'sentients'), @value) - assert_equal(false, @value.transaction_open?) - end - - def test_array - assert_nothing_raised do - @orig = ["first", "second", "third"] - @value = ["first", "second", "third"] - @value.extend(Transaction::Simple) - end - assert_equal(@orig, @value) - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value[1].gsub!(/second/, "fourth") } - assert_not_equal(@orig, @value) - assert_nothing_raised { @value.abort_transaction } - assert_equal(@orig, @value) - end - end - - class Test__Transaction_Simple_ThreadSafe < Test::Unit::TestCase #:nodoc: - VALUE = "Now is the time for all good men to come to the aid of their country." - - def setup - @value = VALUE.dup - @value.extend(Transaction::Simple::ThreadSafe) - end - - def test_extended - assert_respond_to(@value, :start_transaction) - end - - def test_started - assert_equal(false, @value.transaction_open?) - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - end - - def test_rewind - assert_equal(false, @value.transaction_open?) - assert_raises(Transaction::TransactionError) { @value.rewind_transaction } - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.gsub!(/men/, 'women') } - assert_not_equal(VALUE, @value) - assert_nothing_raised { @value.rewind_transaction } - assert_equal(true, @value.transaction_open?) - assert_equal(VALUE, @value) - end - - def test_abort - assert_equal(false, @value.transaction_open?) - assert_raises(Transaction::TransactionError) { @value.abort_transaction } - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.gsub!(/men/, 'women') } - assert_not_equal(VALUE, @value) - assert_nothing_raised { @value.abort_transaction } - assert_equal(false, @value.transaction_open?) - assert_equal(VALUE, @value) - end - - def test_commit - assert_equal(false, @value.transaction_open?) - assert_raises(Transaction::TransactionError) { @value.commit_transaction } - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.gsub!(/men/, 'women') } - assert_not_equal(VALUE, @value) - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.commit_transaction } - assert_equal(false, @value.transaction_open?) - assert_not_equal(VALUE, @value) - end - - def test_multilevel - assert_equal(false, @value.transaction_open?) - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.gsub!(/men/, 'women') } - assert_equal(VALUE.gsub(/men/, 'women'), @value) - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.start_transaction } - assert_nothing_raised { @value.gsub!(/country/, 'nation-state') } - assert_nothing_raised { @value.commit_transaction } - assert_equal(VALUE.gsub(/men/, 'women').gsub(/country/, 'nation-state'), @value) - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value.abort_transaction } - assert_equal(VALUE, @value) - end - - def test_multilevel_named - assert_equal(false, @value.transaction_open?) - assert_raises(Transaction::TransactionError) { @value.transaction_name } - assert_nothing_raised { @value.start_transaction(:first) } # 1 - assert_raises(Transaction::TransactionError) { @value.start_transaction(:first) } - assert_equal(true, @value.transaction_open?) - assert_equal(true, @value.transaction_open?(:first)) - assert_equal(:first, @value.transaction_name) - assert_nothing_raised { @value.start_transaction } # 2 - assert_not_equal(:first, @value.transaction_name) - assert_equal(nil, @value.transaction_name) - assert_raises(Transaction::TransactionError) { @value.abort_transaction(:second) } - assert_nothing_raised { @value.abort_transaction(:first) } - assert_equal(false, @value.transaction_open?) - assert_nothing_raised do - @value.start_transaction(:first) - @value.gsub!(/men/, 'women') - @value.start_transaction(:second) - @value.gsub!(/women/, 'people') - @value.start_transaction - @value.gsub!(/people/, 'sentients') - end - assert_nothing_raised { @value.abort_transaction(:second) } - assert_equal(true, @value.transaction_open?(:first)) - assert_equal(VALUE.gsub(/men/, 'women'), @value) - assert_nothing_raised do - @value.start_transaction(:second) - @value.gsub!(/women/, 'people') - @value.start_transaction - @value.gsub!(/people/, 'sentients') - end - assert_raises(Transaction::TransactionError) { @value.rewind_transaction(:foo) } - assert_nothing_raised { @value.rewind_transaction(:second) } - assert_equal(VALUE.gsub(/men/, 'women'), @value) - assert_nothing_raised do - @value.gsub!(/women/, 'people') - @value.start_transaction - @value.gsub!(/people/, 'sentients') - end - assert_raises(Transaction::TransactionError) { @value.commit_transaction(:foo) } - assert_nothing_raised { @value.commit_transaction(:first) } - assert_equal(VALUE.gsub(/men/, 'sentients'), @value) - assert_equal(false, @value.transaction_open?) - end - - def test_array - assert_nothing_raised do - @orig = ["first", "second", "third"] - @value = ["first", "second", "third"] - @value.extend(Transaction::Simple::ThreadSafe) - end - assert_equal(@orig, @value) - assert_nothing_raised { @value.start_transaction } - assert_equal(true, @value.transaction_open?) - assert_nothing_raised { @value[1].gsub!(/second/, "fourth") } - assert_not_equal(@orig, @value) - assert_nothing_raised { @value.abort_transaction } - assert_equal(@orig, @value) - end - end -end +# :title: Transaction::Simple -- Active Object Transaction Support for Ruby +# :main: Transaction::Simple +# +# == Licence +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +#-- +# Transaction::Simple +# Simple object transaction support for Ruby +# Version 1.3.0 +# +# Copyright (c) 2003 - 2005 Austin Ziegler +# +# $Id: simple.rb,v 1.5 2005/05/05 16:16:49 austin Exp $ +#++ + # The "Transaction" namespace can be used for additional transaction + # support objects and modules. +module Transaction + # A standard exception for transaction errors. + class TransactionError < StandardError; end + # The TransactionAborted exception is used to indicate when a + # transaction has been aborted in the block form. + class TransactionAborted < Exception; end + # The TransactionCommitted exception is used to indicate when a + # transaction has been committed in the block form. + class TransactionCommitted < Exception; end + + te = "Transaction Error: %s" + + Messages = { + :bad_debug_object => + te % "the transaction debug object must respond to #<<.", + :unique_names => + te % "named transactions must be unique.", + :no_transaction_open => + te % "no transaction open.", + :cannot_rewind_no_transaction => + te % "cannot rewind; there is no current transaction.", + :cannot_rewind_named_transaction => + te % "cannot rewind to transaction %s because it does not exist.", + :cannot_rewind_transaction_before_block => + te % "cannot rewind a transaction started before the execution block.", + :cannot_abort_no_transaction => + te % "cannot abort; there is no current transaction.", + :cannot_abort_transaction_before_block => + te % "cannot abort a transaction started before the execution block.", + :cannot_abort_named_transaction => + te % "cannot abort nonexistant transaction %s.", + :cannot_commit_no_transaction => + te % "cannot commit; there is no current transaction.", + :cannot_commit_transaction_before_block => + te % "cannot commit a transaction started before the execution block.", + :cannot_commit_named_transaction => + te % "cannot commit nonexistant transaction %s.", + :cannot_start_empty_block_transaction => + te % "cannot start a block transaction with no objects.", + :cannot_obtain_transaction_lock => + te % "cannot obtain transaction lock for #%s.", + } + + # = Transaction::Simple for Ruby + # Simple object transaction support for Ruby + # + # == Introduction + # Transaction::Simple provides a generic way to add active transaction + # support to objects. The transaction methods added by this module will + # work with most objects, excluding those that cannot be + # Marshaled (bindings, procedure objects, IO instances, or + # singleton objects). + # + # The transactions supported by Transaction::Simple are not backed + # transactions; they are not associated with any sort of data store. + # They are "live" transactions occurring in memory and in the object + # itself. This is to allow "test" changes to be made to an object + # before making the changes permanent. + # + # Transaction::Simple can handle an "infinite" number of transaction + # levels (limited only by memory). If I open two transactions, commit + # the second, but abort the first, the object will revert to the + # original version. + # + # Transaction::Simple supports "named" transactions, so that multiple + # levels of transactions can be committed, aborted, or rewound by + # referring to the appropriate name of the transaction. Names may be any + # object *except* +nil+. As with Hash keys, String names will be + # duplicated and frozen before using. + # + # Copyright:: Copyright © 2003 - 2005 by Austin Ziegler + # Version:: 1.3.0 + # Licence:: MIT-Style + # + # Thanks to David Black for help with the initial concept that led to + # this library. + # + # == Usage + # include 'transaction/simple' + # + # v = "Hello, you." # -> "Hello, you." + # v.extend(Transaction::Simple) # -> "Hello, you." + # + # v.start_transaction # -> ... (a Marshal string) + # v.transaction_open? # -> true + # v.gsub!(/you/, "world") # -> "Hello, world." + # + # v.rewind_transaction # -> "Hello, you." + # v.transaction_open? # -> true + # + # v.gsub!(/you/, "HAL") # -> "Hello, HAL." + # v.abort_transaction # -> "Hello, you." + # v.transaction_open? # -> false + # + # v.start_transaction # -> ... (a Marshal string) + # v.start_transaction # -> ... (a Marshal string) + # + # v.transaction_open? # -> true + # v.gsub!(/you/, "HAL") # -> "Hello, HAL." + # + # v.commit_transaction # -> "Hello, HAL." + # v.transaction_open? # -> true + # v.abort_transaction # -> "Hello, you." + # v.transaction_open? # -> false + # + # == Named Transaction Usage + # v = "Hello, you." # -> "Hello, you." + # v.extend(Transaction::Simple) # -> "Hello, you." + # + # v.start_transaction(:first) # -> ... (a Marshal string) + # v.transaction_open? # -> true + # v.transaction_open?(:first) # -> true + # v.transaction_open?(:second) # -> false + # v.gsub!(/you/, "world") # -> "Hello, world." + # + # v.start_transaction(:second) # -> ... (a Marshal string) + # v.gsub!(/world/, "HAL") # -> "Hello, HAL." + # v.rewind_transaction(:first) # -> "Hello, you." + # v.transaction_open? # -> true + # v.transaction_open?(:first) # -> true + # v.transaction_open?(:second) # -> false + # + # v.gsub!(/you/, "world") # -> "Hello, world." + # v.start_transaction(:second) # -> ... (a Marshal string) + # v.gsub!(/world/, "HAL") # -> "Hello, HAL." + # v.transaction_name # -> :second + # v.abort_transaction(:first) # -> "Hello, you." + # v.transaction_open? # -> false + # + # v.start_transaction(:first) # -> ... (a Marshal string) + # v.gsub!(/you/, "world") # -> "Hello, world." + # v.start_transaction(:second) # -> ... (a Marshal string) + # v.gsub!(/world/, "HAL") # -> "Hello, HAL." + # + # v.commit_transaction(:first) # -> "Hello, HAL." + # v.transaction_open? # -> false + # + # == Block Usage + # v = "Hello, you." # -> "Hello, you." + # Transaction::Simple.start(v) do |tv| + # # v has been extended with Transaction::Simple and an unnamed + # # transaction has been started. + # tv.transaction_open? # -> true + # tv.gsub!(/you/, "world") # -> "Hello, world." + # + # tv.rewind_transaction # -> "Hello, you." + # tv.transaction_open? # -> true + # + # tv.gsub!(/you/, "HAL") # -> "Hello, HAL." + # # The following breaks out of the transaction block after + # # aborting the transaction. + # tv.abort_transaction # -> "Hello, you." + # end + # # v still has Transaction::Simple applied from here on out. + # v.transaction_open? # -> false + # + # Transaction::Simple.start(v) do |tv| + # tv.start_transaction # -> ... (a Marshal string) + # + # tv.transaction_open? # -> true + # tv.gsub!(/you/, "HAL") # -> "Hello, HAL." + # + # # If #commit_transaction were called without having started a + # # second transaction, then it would break out of the transaction + # # block after committing the transaction. + # tv.commit_transaction # -> "Hello, HAL." + # tv.transaction_open? # -> true + # tv.abort_transaction # -> "Hello, you." + # end + # v.transaction_open? # -> false + # + # == Named Transaction Usage + # v = "Hello, you." # -> "Hello, you." + # v.extend(Transaction::Simple) # -> "Hello, you." + # + # v.start_transaction(:first) # -> ... (a Marshal string) + # v.transaction_open? # -> true + # v.transaction_open?(:first) # -> true + # v.transaction_open?(:second) # -> false + # v.gsub!(/you/, "world") # -> "Hello, world." + # + # v.start_transaction(:second) # -> ... (a Marshal string) + # v.gsub!(/world/, "HAL") # -> "Hello, HAL." + # v.rewind_transaction(:first) # -> "Hello, you." + # v.transaction_open? # -> true + # v.transaction_open?(:first) # -> true + # v.transaction_open?(:second) # -> false + # + # v.gsub!(/you/, "world") # -> "Hello, world." + # v.start_transaction(:second) # -> ... (a Marshal string) + # v.gsub!(/world/, "HAL") # -> "Hello, HAL." + # v.transaction_name # -> :second + # v.abort_transaction(:first) # -> "Hello, you." + # v.transaction_open? # -> false + # + # v.start_transaction(:first) # -> ... (a Marshal string) + # v.gsub!(/you/, "world") # -> "Hello, world." + # v.start_transaction(:second) # -> ... (a Marshal string) + # v.gsub!(/world/, "HAL") # -> "Hello, HAL." + # + # v.commit_transaction(:first) # -> "Hello, HAL." + # v.transaction_open? # -> false + # + # == Thread Safety + # Threadsafe version of Transaction::Simple and + # Transaction::Simple::Group exist; these are loaded from + # 'transaction/simple/threadsafe' and + # 'transaction/simple/threadsafe/group', respectively, and are + # represented in Ruby code as Transaction::Simple::ThreadSafe and + # Transaction::Simple::ThreadSafe::Group, respectively. + # + # == Contraindications + # While Transaction::Simple is very useful, it has some severe + # limitations that must be understood. Transaction::Simple: + # + # * uses Marshal. Thus, any object which cannot be Marshaled + # cannot use Transaction::Simple. In my experience, this affects + # singleton objects more often than any other object. It may be that + # Ruby 2.0 will solve this problem. + # * does not manage resources. Resources external to the object and its + # instance variables are not managed at all. However, all instance + # variables and objects "belonging" to those instance variables are + # managed. If there are object reference counts to be handled, + # Transaction::Simple will probably cause problems. + # * is not inherently thread-safe. In the ACID ("atomic, consistent, + # isolated, durable") test, Transaction::Simple provides CD, but it is + # up to the user of Transaction::Simple to provide isolation and + # atomicity. Transactions should be considered "critical sections" in + # multi-threaded applications. If thread safety and atomicity is + # absolutely required, use Transaction::Simple::ThreadSafe, which uses + # a Mutex object to synchronize the accesses on the object during the + # transaction operations. + # * does not necessarily maintain Object#__id__ values on rewind or + # abort. This may change for future versions that will be Ruby 1.8 or + # better *only*. Certain objects that support #replace will maintain + # Object#__id__. + # * Can be a memory hog if you use many levels of transactions on many + # objects. + # + module Simple + TRANSACTION_SIMPLE_VERSION = '1.3.0' + + # Sets the Transaction::Simple debug object. It must respond to #<<. + # Sets the transaction debug object. Debugging will be performed + # automatically if there's a debug object. The generic transaction + # error class. + def self.debug_io=(io) + if io.nil? + @tdi = nil + @debugging = false + else + unless io.respond_to?(:<<) + raise TransactionError, Messages[:bad_debug_object] + end + @tdi = io + @debugging = true + end + end + + # Returns +true+ if we are debugging. + def self.debugging? + @debugging + end + + # Returns the Transaction::Simple debug object. It must respond to + # #<<. + def self.debug_io + @tdi ||= "" + @tdi + end + + # If +name+ is +nil+ (default), then returns +true+ if there is + # currently a transaction open. + # + # If +name+ is specified, then returns +true+ if there is currently a + # transaction that responds to +name+ open. + def transaction_open?(name = nil) + if name.nil? + if Transaction::Simple.debugging? + Transaction::Simple.debug_io << "Transaction " << + "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" + end + return (not @__transaction_checkpoint__.nil?) + else + if Transaction::Simple.debugging? + Transaction::Simple.debug_io << "Transaction(#{name.inspect}) " << + "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" + end + return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name)) + end + end + + # Returns the current name of the transaction. Transactions not + # explicitly named are named +nil+. + def transaction_name + if @__transaction_checkpoint__.nil? + raise TransactionError, Messages[:no_transaction_open] + end + if Transaction::Simple.debugging? + Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << + "Transaction Name: #{@__transaction_names__[-1].inspect}\n" + end + if @__transaction_names__[-1].kind_of?(String) + @__transaction_names__[-1].dup + else + @__transaction_names__[-1] + end + end + + # Starts a transaction. Stores the current object state. If a + # transaction name is specified, the transaction will be named. + # Transaction names must be unique. Transaction names of +nil+ will be + # treated as unnamed transactions. + def start_transaction(name = nil) + @__transaction_level__ ||= 0 + @__transaction_names__ ||= [] + + if name.nil? + @__transaction_names__ << nil + ss = "" if Transaction::Simple.debugging? + else + if @__transaction_names__.include?(name) + raise TransactionError, Messages[:unique_names] + end + name = name.dup.freeze if name.kind_of?(String) + @__transaction_names__ << name + ss = "(#{name.inspect})" if Transaction::Simple.debugging? + end + + @__transaction_level__ += 1 + + if Transaction::Simple.debugging? + Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} " << + "Start Transaction#{ss}\n" + end + + @__transaction_checkpoint__ = Marshal.dump(self) + end + + # Rewinds the transaction. If +name+ is specified, then the + # intervening transactions will be aborted and the named transaction + # will be rewound. Otherwise, only the current transaction is rewound. + def rewind_transaction(name = nil) + if @__transaction_checkpoint__.nil? + raise TransactionError, Messages[:cannot_rewind_no_transaction] + end + + # Check to see if we are trying to rewind a transaction that is + # outside of the current transaction block. + if @__transaction_block__ and name + nix = @__transaction_names__.index(name) + 1 + if nix < @__transaction_block__ + raise TransactionError, Messages[:cannot_rewind_transaction_before_block] + end + end + + if name.nil? + __rewind_this_transaction + ss = "" if Transaction::Simple.debugging? + else + unless @__transaction_names__.include?(name) + raise TransactionError, Messages[:cannot_rewind_named_transaction] % name.inspect + end + ss = "(#{name})" if Transaction::Simple.debugging? + + while @__transaction_names__[-1] != name + @__transaction_checkpoint__ = __rewind_this_transaction + if Transaction::Simple.debugging? + Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << + "Rewind Transaction#{ss}\n" + end + @__transaction_level__ -= 1 + @__transaction_names__.pop + end + __rewind_this_transaction + end + if Transaction::Simple.debugging? + Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << + "Rewind Transaction#{ss}\n" + end + self + end + + # Aborts the transaction. Resets the object state to what it was + # before the transaction was started and closes the transaction. If + # +name+ is specified, then the intervening transactions and the named + # transaction will be aborted. Otherwise, only the current transaction + # is aborted. + # + # If the current or named transaction has been started by a block + # (Transaction::Simple.start), then the execution of the block will be + # halted with +break+ +self+. + def abort_transaction(name = nil) + if @__transaction_checkpoint__.nil? + raise TransactionError, Messages[:cannot_abort_no_transaction] + end + + # Check to see if we are trying to abort a transaction that is + # outside of the current transaction block. Otherwise, raise + # TransactionAborted if they are the same. + if @__transaction_block__ and name + nix = @__transaction_names__.index(name) + 1 + if nix < @__transaction_block__ + raise TransactionError, Messages[:cannot_abort_transaction_before_block] + end + + raise TransactionAborted if @__transaction_block__ == nix + end + + raise TransactionAborted if @__transaction_block__ == @__transaction_level__ + + if name.nil? + __abort_transaction(name) + else + unless @__transaction_names__.include?(name) + raise TransactionError, Messages[:cannot_abort_named_transaction] % name.inspect + end + __abort_transaction(name) while @__transaction_names__.include?(name) + end + self + end + + # If +name+ is +nil+ (default), the current transaction level is + # closed out and the changes are committed. + # + # If +name+ is specified and +name+ is in the list of named + # transactions, then all transactions are closed and committed until + # the named transaction is reached. + def commit_transaction(name = nil) + if @__transaction_checkpoint__.nil? + raise TransactionError, Messages[:cannot_commit_no_transaction] + end + @__transaction_block__ ||= nil + + # Check to see if we are trying to commit a transaction that is + # outside of the current transaction block. Otherwise, raise + # TransactionCommitted if they are the same. + if @__transaction_block__ and name + nix = @__transaction_names__.index(name) + 1 + if nix < @__transaction_block__ + raise TransactionError, Messages[:cannot_commit_transaction_before_block] + end + + raise TransactionCommitted if @__transaction_block__ == nix + end + + raise TransactionCommitted if @__transaction_block__ == @__transaction_level__ + + if name.nil? + ss = "" if Transaction::Simple.debugging? + __commit_transaction + if Transaction::Simple.debugging? + Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << + "Commit Transaction#{ss}\n" + end + else + unless @__transaction_names__.include?(name) + raise TransactionError, Messages[:cannot_commit_named_transaction] % name.inspect + end + ss = "(#{name})" if Transaction::Simple.debugging? + + while @__transaction_names__[-1] != name + if Transaction::Simple.debugging? + Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << + "Commit Transaction#{ss}\n" + end + __commit_transaction + end + if Transaction::Simple.debugging? + Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << + "Commit Transaction#{ss}\n" + end + __commit_transaction + end + + self + end + + # Alternative method for calling the transaction methods. An optional + # name can be specified for named transaction support. + # + # #transaction(:start):: #start_transaction + # #transaction(:rewind):: #rewind_transaction + # #transaction(:abort):: #abort_transaction + # #transaction(:commit):: #commit_transaction + # #transaction(:name):: #transaction_name + # #transaction:: #transaction_open? + def transaction(action = nil, name = nil) + case action + when :start + start_transaction(name) + when :rewind + rewind_transaction(name) + when :abort + abort_transaction(name) + when :commit + commit_transaction(name) + when :name + transaction_name + when nil + transaction_open?(name) + end + end + + # Allows specific variables to be excluded from transaction support. + # Must be done after extending the object but before starting the + # first transaction on the object. + # + # vv.transaction_exclusions << "@io" + def transaction_exclusions + @transaction_exclusions ||= [] + end + + class << self + def __common_start(name, vars, &block) + if vars.empty? + raise TransactionError, Messages[:cannot_start_empty_block_transaction] + end + + if block + begin + vlevel = {} + + vars.each do |vv| + vv.extend(Transaction::Simple) + vv.start_transaction(name) + vlevel[vv.__id__] = vv.instance_variable_get(:@__transaction_level__) + vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__]) + end + + yield(*vars) + rescue TransactionAborted + vars.each do |vv| + if name.nil? and vv.transaction_open? + loop do + tlevel = vv.instance_variable_get(:@__transaction_level__) || -1 + vv.instance_variable_set(:@__transaction_block__, -1) + break if tlevel < vlevel[vv.__id__] + vv.abort_transaction if vv.transaction_open? + end + elsif vv.transaction_open?(name) + vv.instance_variable_set(:@__transaction_block__, -1) + vv.abort_transaction(name) + end + end + rescue TransactionCommitted + nil + ensure + vars.each do |vv| + if name.nil? and vv.transaction_open? + loop do + tlevel = vv.instance_variable_get(:@__transaction_level__) || -1 + break if tlevel < vlevel[vv.__id__] + vv.instance_variable_set(:@__transaction_block__, -1) + vv.commit_transaction if vv.transaction_open? + end + elsif vv.transaction_open?(name) + vv.instance_variable_set(:@__transaction_block__, -1) + vv.commit_transaction(name) + end + end + end + else + vars.each do |vv| + vv.extend(Transaction::Simple) + vv.start_transaction(name) + end + end + end + private :__common_start + + def start_named(name, *vars, &block) + __common_start(name, vars, &block) + end + + def start(*vars, &block) + __common_start(nil, vars, &block) + end + end + + def __abort_transaction(name = nil) #:nodoc: + @__transaction_checkpoint__ = __rewind_this_transaction + + if name.nil? + ss = "" if Transaction::Simple.debugging? + else + ss = "(#{name.inspect})" if Transaction::Simple.debugging? + end + + if Transaction::Simple.debugging? + Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << + "Abort Transaction#{ss}\n" + end + @__transaction_level__ -= 1 + @__transaction_names__.pop + if @__transaction_level__ < 1 + @__transaction_level__ = 0 + @__transaction_names__ = [] + end + end + + TRANSACTION_CHECKPOINT = "@__transaction_checkpoint__" #:nodoc: + SKIP_TRANSACTION_VARS = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc: + + def __rewind_this_transaction #:nodoc: + rr = Marshal.restore(@__transaction_checkpoint__) + + begin + self.replace(rr) if respond_to?(:replace) + rescue + nil + end + + rr.instance_variables.each do |vv| + next if SKIP_TRANSACTION_VARS.include?(vv) + next if self.transaction_exclusions.include?(vv) + if respond_to?(:instance_variable_get) + instance_variable_set(vv, rr.instance_variable_get(vv)) + else + instance_eval(%q|#{vv} = rr.instance_eval("#{vv}")|) + end + end + + new_ivar = instance_variables - rr.instance_variables - SKIP_TRANSACTION_VARS + new_ivar.each do |vv| + if respond_to?(:instance_variable_set) + instance_variable_set(vv, nil) + else + instance_eval(%q|#{vv} = nil|) + end + end + + if respond_to?(:instance_variable_get) + rr.instance_variable_get(TRANSACTION_CHECKPOINT) + else + rr.instance_eval(TRANSACTION_CHECKPOINT) + end + end + + def __commit_transaction #:nodoc: + if respond_to?(:instance_variable_get) + @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_variable_get(TRANSACTION_CHECKPOINT) + else + @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_eval(TRANSACTION_CHECKPOINT) + end + + @__transaction_level__ -= 1 + @__transaction_names__.pop + + if @__transaction_level__ < 1 + @__transaction_level__ = 0 + @__transaction_names__ = [] + end + end + + private :__abort_transaction + private :__rewind_this_transaction + private :__commit_transaction + end +end -- cgit v1.2.3