transaction-simple-1.4.0.2/0000755000004100000410000000000012014527406015521 5ustar www-datawww-datatransaction-simple-1.4.0.2/test/0000755000004100000410000000000012014527406016500 5ustar www-datawww-datatransaction-simple-1.4.0.2/test/test_transaction_simple_group.rb0000644000004100000410000000304712014527406025202 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 require 'transaction/simple/group' require 'test/unit' module Transaction::Simple::Test class Group < Test::Unit::TestCase #:nodoc: VALUE1 = "Hello, you." VALUE2 = "And you, too." def setup @x = VALUE1.dup @y = VALUE2.dup end def test_group group = Transaction::Simple::Group.new(@x, @y) assert_nothing_raised { group.start_transaction(:first) } assert_equal(true, group.transaction_open?(:first)) assert_equal(true, @x.transaction_open?(:first)) assert_equal(true, @y.transaction_open?(:first)) assert_equal("Hello, world.", @x.gsub!(/you/, "world")) assert_equal("And me, too.", @y.gsub!(/you/, "me")) assert_nothing_raised { group.start_transaction(:second) } assert_equal("Hello, HAL.", @x.gsub!(/world/, "HAL")) assert_equal("And Dave, too.", @y.gsub!(/me/, "Dave")) assert_nothing_raised { group.rewind_transaction(:second) } assert_equal("Hello, world.", @x) assert_equal("And me, too.", @y) assert_equal("Hello, HAL.", @x.gsub!(/world/, "HAL")) assert_equal("And Dave, too.", @y.gsub!(/me/, "Dave")) assert_nothing_raised { group.commit_transaction(:second) } assert_equal("Hello, HAL.", @x) assert_equal("And Dave, too.", @y) assert_nothing_raised { group.abort_transaction(:first) } assert_equal("Hello, you.", @x) assert_equal("And you, too.", @y) end end end # vim: syntax=ruby transaction-simple-1.4.0.2/test/test_transaction_simple.rb0000644000004100000410000002657012014527406023774 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 require 'transaction/simple' require 'test/unit' module Transaction::Simple::Test class TransactionSimple < Test::Unit::TestCase #:nodoc: VALUE = "Now is the time for all good men to come to the aid of their country." class Value def initialize @value = VALUE.dup end def method_missing(meth, *args, &block) @value.__send__(meth, *args, &block) end def ==(other) other == @value end def to_str @value end end def setup @value = Value.new @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_block Transaction::Simple.start(@value) do |tv| assert_equal(true, tv.transaction_open?) assert_nothing_raised { tv.gsub!(/men/, 'women') } assert_equal(VALUE.gsub(/men/, 'women'), tv) tv.abort_transaction flunk("Failed to abort the transaction.") end assert_equal(false, @value.transaction_open?) assert_equal(VALUE, @value) @value = VALUE.dup Transaction::Simple.start(@value) do |tv| assert_equal(true, tv.transaction_open?) assert_nothing_raised { tv.gsub!(/men/, 'women') } assert_equal(VALUE.gsub(/men/, 'women'), tv) tv.commit_transaction flunk("Failed to commit the transaction.") end assert_equal(false, @value.transaction_open?) assert_equal(VALUE.gsub(/men/, 'women'), @value) end def test_named_block Transaction::Simple.start_named(:first, @value) do |tv| assert_equal(true, tv.transaction_open?) assert_equal(true, tv.transaction_open?(:first)) assert_nothing_raised { tv.gsub!(/men/, 'women') } assert_equal(VALUE.gsub(/men/, 'women'), tv) tv.abort_transaction flunk("Failed to abort the transaction.") end assert_equal(false, @value.transaction_open?) assert_equal(false, @value.transaction_open?(:first)) assert_equal(VALUE, @value) @value = VALUE.dup Transaction::Simple.start_named(:first, @value) do |tv| assert_equal(true, tv.transaction_open?) assert_equal(true, tv.transaction_open?(:first)) assert_nothing_raised { tv.gsub!(/men/, 'women') } assert_equal(VALUE.gsub(/men/, 'women'), tv) tv.commit_transaction flunk("Failed to commit the transaction.") end assert_equal(false, @value.transaction_open?) assert_equal(false, @value.transaction_open?(:first)) assert_equal(VALUE.gsub(/men/, 'women'), @value) end def test_named_block_error @value.start_transaction(:first) Transaction::Simple.start_named(:second, @value) do |tv| assert_equal(true, tv.transaction_open?) assert_equal(true, tv.transaction_open?(:first)) assert_equal(true, tv.transaction_open?(:second)) assert_nothing_raised { tv.gsub!(/men/, 'women') } assert_equal(VALUE.gsub(/men/, 'women'), tv) assert_raises(Transaction::TransactionError) do tv.abort_transaction(:first) end end assert_equal(true, @value.transaction_open?) assert_equal(true, @value.transaction_open?(:first)) assert_equal(false, @value.transaction_open?(:second)) assert_equal(VALUE.gsub(/men/, 'women'), @value) assert_nothing_raised { @value.abort_transaction(:first) } assert_equal(VALUE, @value) @value.start_transaction(:first) Transaction::Simple.start_named(:second, @value) do |tv| assert_equal(true, tv.transaction_open?) assert_equal(true, tv.transaction_open?(:first)) assert_equal(true, tv.transaction_open?(:second)) assert_nothing_raised { tv.gsub!(/men/, 'women') } assert_equal(VALUE.gsub(/men/, 'women'), tv) assert_raises(Transaction::TransactionError) do tv.commit_transaction(:first) end end assert_equal(true, @value.transaction_open?) assert_equal(true, @value.transaction_open?(:first)) assert_equal(false, @value.transaction_open?(:second)) assert_equal(VALUE.gsub(/men/, 'women'), @value) assert_nothing_raised { @value.abort_transaction(:first) } assert_equal(VALUE, @value) end def test_multivar_block a = VALUE.dup b = a.dup Transaction::Simple.start(a, b) do |ta, tb| assert_equal(true, ta.transaction_open?) assert_equal(true, tb.transaction_open?) ta.abort_transaction flunk("Failed to abort the transaction.") end assert_equal(false, a.transaction_open?) assert_equal(false, b.transaction_open?) Transaction::Simple.start(a, b) do |ta, tb| assert_equal(true, ta.transaction_open?) assert_equal(true, tb.transaction_open?) ta.commit_transaction flunk("Failed to commit the transaction.") end assert_equal(false, a.transaction_open?) assert_equal(false, b.transaction_open?) end def test_multilevel_block Transaction::Simple.start_named(:outer, @value) do |tv0| assert_equal(1, tv0.instance_variable_get(:@__transaction_level__)) assert_equal(true, tv0.transaction_open?(:outer)) Transaction::Simple.start_named(:inner, tv0) do |tv1| assert_equal(2, tv0.instance_variable_get(:@__transaction_level__)) assert_equal(true, tv1.transaction_open?(:inner)) tv1.abort_transaction flunk("Failed to abort the transaction.") end assert_equal(false, tv0.transaction_open?(:inner)) assert_equal(true, tv0.transaction_open?(:outer)) tv0.commit_transaction flunk("Failed to commit the transaction.") end assert_equal(false, @value.transaction_open?(:inner)) assert_equal(false, @value.transaction_open?(:outer)) 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 def test_instance_var @value = VALUE.dup @value.extend(Transaction::Simple) @value.start_transaction assert_equal(true, @value.transaction_open?) @value.instance_variable_set("@foo", "bar") @value.rewind_transaction assert_equal(false, @value.instance_variables.include?("@foo")) end end end # vim: syntax=ruby transaction-simple-1.4.0.2/test/test_transaction_simple_threadsafe.rb0000644000004100000410000001326112014527406026153 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 require 'transaction/simple/threadsafe' require 'test/unit' module Transaction::Simple::Test class 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 # vim: syntax=ruby transaction-simple-1.4.0.2/test/test_broken_graph.rb0000755000004100000410000000314612014527406022534 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 require 'transaction/simple' require 'test/unit' module Transaction::Simple::Test class BrokenGraph < Test::Unit::TestCase #:nodoc: class Child attr_accessor :parent end class BrokenParent include Transaction::Simple attr_reader :children def initialize @children = [] end def <<(child) child.parent = self @children << child end end class FixedParent < BrokenParent # Reconnect the restored children to me, instead of to the bogus me # that was restored to them by Marshal::load. def _post_transaction_rewind @children.each { |child| child.parent = self } end end def test_broken_graph parent = BrokenParent.new parent << Child.new assert_equal(parent.object_id, parent.children[0].parent.object_id) parent.start_transaction parent << Child.new assert_equal(parent.object_id, parent.children[1].parent.object_id) parent.abort_transaction assert_not_equal(parent.object_id, parent.children[0].parent.object_id) end def test_fixed_graph parent = FixedParent.new parent << Child.new assert_equal(parent.object_id, parent.children[0].parent.object_id) parent.start_transaction parent << Child.new assert_equal(parent.object_id, parent.children[1].parent.object_id) parent.abort_transaction assert_equal(parent.object_id, parent.children[0].parent.object_id) end end end # vim: syntax=ruby transaction-simple-1.4.0.2/README.rdoc0000644000004100000410000002202712014527406017332 0ustar www-datawww-data= Transaction::Simple for Ruby home :: http://trans-simple.rubyforge.org/ code :: https://github.com/halostatue/transaction-simple bugs :: https://github.com/halostatue/transaction-simple/issues rdoc :: http://trans-simple.rubyforge.org/ == Description 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 Marshal-ed (bindings, procedure objects, IO instances, or singleton objects). The transactions supported by Transaction::Simple are not associated with any sort of data store. They are "live" transactions occurring in memory on 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. Transaction groups are also supported. A transaction group is an object wrapper that manages a group of objects as if they were a single object for the purpose of transaction management. All transactions for this group of objects should be performed against the transaction group object, not against individual objects in the group. Version 1.4.0 of Transaction::Simple adds a new post-rewind hook so that complex graph objects of the type in tests/tc_broken_graph.rb can correct themselves. Version 1.4.0.1 just fixes a simple bug with #transaction method handling during the deprecation warning. Version 1.4.0.2 is a small update for people who use Transaction::Simple in bundler (adding lib/transaction-simple.rb) and other scenarios where having Hoe as a runtime dependency (a bug fixed in Hoe several years ago, but not visible in Transaction::Simple because it has not needed a re-release). All of the files internally have also been marked as UTF-8, ensuring full Ruby 1.9 compatibility. == Usage require '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 Transaction 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 == Transaction Groups require 'transaction/simple/group' x = "Hello, you." y = "And you, too." g = Transaction::Simple::Group.new(x, y) g.start_transaction(:first) # -> [ x, y ] g.transaction_open?(:first) # -> true x.transaction_open?(:first) # -> true y.transaction_open?(:first) # -> true x.gsub!(/you/, "world") # -> "Hello, world." y.gsub!(/you/, "me") # -> "And me, too." g.start_transaction(:second) # -> [ x, y ] x.gsub!(/world/, "HAL") # -> "Hello, HAL." y.gsub!(/me/, "Dave") # -> "And Dave, too." g.rewind_transaction(:second) # -> [ x, y ] x # -> "Hello, world." y # -> "And me, too." x.gsub!(/world/, "HAL") # -> "Hello, HAL." y.gsub!(/me/, "Dave") # -> "And Dave, too." g.commit_transaction(:second) # -> [ x, y ] x # -> "Hello, HAL." y # -> "And Dave, too." g.abort_transaction(:first) # -> [ x, y ] x = -> "Hello, you." y = -> "And you, too." == Thread Safety Threadsafe versions 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 limitations that must be understood prior to using it. Transaction::Simple: * uses Marshal. Thus, any object which cannot be Marshal-ed cannot use Transaction::Simple. In my experience, this affects singleton objects more often than any other object. * does not manage external 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 thread-safe. In the ACID ("atomic, consistent, isolated, durable") test, Transaction::Simple provides consistency and durability, but cannot itself provide isolation. Transactions should be considered "critical sections" in multi-threaded applications. Thread safety of the transaction acquisition and release process itself can be ensured with the thread-safe version, Transaction::Simple::ThreadSafe. With transaction groups, some level of atomicity is assured. * does not maintain Object#__id__ values on rewind or abort. This only affects complex self-referential graphs. tests/tc_broken_graph.rb demonstrates this and its mitigation with the new post-rewind hook. #_post_transaction_rewind. Matz has implemented an experimental feature in Ruby 1.9 that may find its way into the released Ruby 1.9.1 and ultimately Ruby 2.0 that would obviate the need for #_post_transaction_rewind. Pit Capitain has also suggested a workaround that does not require changes to core Ruby, but does not work in all cases. A final resolution is still pending further discussion. * Can be a memory hog if you use many levels of transactions on many objects. :include: Licence.rdoc transaction-simple-1.4.0.2/Rakefile0000644000004100000410000000075212014527406017172 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- require 'rubygems' require 'hoe' Hoe.plugin :doofus Hoe.plugin :gemspec Hoe.plugin :rubyforge Hoe.plugin :git Hoe.spec 'transaction-simple' do self.rubyforge_name = "trans-simple" developer('Austin Ziegler', 'austin@rubyforge.org') self.remote_rdoc_dir = '' self.rsync_args << ' --exclude=statsvn/' self.history_file = 'History.rdoc' self.readme_file = 'README.rdoc' self.extra_rdoc_files = FileList["*.rdoc"].to_a end # vim: syntax=ruby transaction-simple-1.4.0.2/.gemtest0000644000004100000410000000000012014527406017160 0ustar www-datawww-datatransaction-simple-1.4.0.2/Licence.rdoc0000644000004100000410000000216312014527406017736 0ustar www-datawww-data== Licence This software is available under the terms of the MIT license. * Copyright 2003–2012 Austin Ziegler 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-1.4.0.2/metadata.yml0000644000004100000410000001157212014527406020032 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: transaction-simple version: !ruby/object:Gem::Version version: 1.4.0.2 prerelease: platform: ruby authors: - Austin Ziegler autorequire: bindir: bin cert_chain: [] date: 2012-06-21 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rubyforge requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 2.0.4 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 2.0.4 - !ruby/object:Gem::Dependency name: rdoc requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '3.10' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '3.10' - !ruby/object:Gem::Dependency name: hoe requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '3.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '3.0' description: ! '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 Marshal-ed (bindings, procedure objects, IO instances, or singleton objects). The transactions supported by Transaction::Simple are not associated with any sort of data store. They are "live" transactions occurring in memory on 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. Transaction groups are also supported. A transaction group is an object wrapper that manages a group of objects as if they were a single object for the purpose of transaction management. All transactions for this group of objects should be performed against the transaction group object, not against individual objects in the group. Version 1.4.0 of Transaction::Simple adds a new post-rewind hook so that complex graph objects of the type in tests/tc_broken_graph.rb can correct themselves. Version 1.4.0.1 just fixes a simple bug with #transaction method handling during the deprecation warning. Version 1.4.0.2 is a small update for people who use Transaction::Simple in bundler (adding lib/transaction-simple.rb) and other scenarios where having Hoe as a runtime dependency (a bug fixed in Hoe several years ago, but not visible in Transaction::Simple because it has not needed a re-release). All of the files internally have also been marked as UTF-8, ensuring full Ruby 1.9 compatibility.' email: - austin@rubyforge.org executables: [] extensions: [] extra_rdoc_files: - History.rdoc - Licence.rdoc - Manifest.txt - README.rdoc files: - History.rdoc - Licence.rdoc - Manifest.txt - README.rdoc - Rakefile - lib/transaction-simple.rb - lib/transaction/simple.rb - lib/transaction/simple/group.rb - lib/transaction/simple/threadsafe.rb - lib/transaction/simple/threadsafe/group.rb - research/instance_variable_defined.rb - research/special-dumpable-string.rb - research/special-dumpable.rb - test/test_broken_graph.rb - test/test_transaction_simple.rb - test/test_transaction_simple_group.rb - test/test_transaction_simple_threadsafe.rb - .gemtest homepage: http://trans-simple.rubyforge.org/ licenses: [] post_install_message: rdoc_options: - --main - README.rdoc require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: trans-simple rubygems_version: 1.8.21 signing_key: specification_version: 3 summary: Transaction::Simple provides a generic way to add active transaction support to objects test_files: - test/test_broken_graph.rb - test/test_transaction_simple.rb - test/test_transaction_simple_group.rb - test/test_transaction_simple_threadsafe.rb transaction-simple-1.4.0.2/Manifest.txt0000644000004100000410000000072212014527406020031 0ustar www-datawww-dataHistory.rdoc Licence.rdoc Manifest.txt README.rdoc Rakefile lib/transaction-simple.rb lib/transaction/simple.rb lib/transaction/simple/group.rb lib/transaction/simple/threadsafe.rb lib/transaction/simple/threadsafe/group.rb research/instance_variable_defined.rb research/special-dumpable-string.rb research/special-dumpable.rb test/test_broken_graph.rb test/test_transaction_simple.rb test/test_transaction_simple_group.rb test/test_transaction_simple_threadsafe.rb transaction-simple-1.4.0.2/research/0000755000004100000410000000000012014527406017315 5ustar www-datawww-datatransaction-simple-1.4.0.2/research/instance_variable_defined.rb0000644000004100000410000000102212014527406024764 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- # Provided by Nobu Nakada. # You can use this code for previous versions. unless defined?(instance_variable_defined?) module Kernel (t = Object.new).instance_eval { @instance_variable = 1 } case t.instance_variables[0] when Symbol def instance_variable_defined?(var) instance_variables.include?(var.to_sym) end when String def instance_variable_defined?(var) instance_variables.include?(var.to_s) end end end end # vim: syntax=ruby transaction-simple-1.4.0.2/research/special-dumpable.rb0000755000004100000410000001012712014527406023055 0ustar www-datawww-data#!/usr/bin/env ruby # -*- ruby encoding: utf-8 -*- # # SpecialDumpable - workaround for a problem in Transaction::Simple # # (C) 2006 Pit Capitain # # For a description of the problem see http://www.halostatue.ca/2006/10/22/ # ruby-conference-2006-day-1-evening-friday-20-october-2006/ # and http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155092 # # The workaround: # # The objects that are restored from Marshal shouldn't reference the newly # created root object, but the original root object. After studying the # source code of Marshal, I found that this could be achieved with a # special _load method of the root object's class which returns the # original root object. Then the original root object would be stored in # the internal list of restored objects and references to the root object # would use the original one. # # See SpecialDumpable::ClassMethods#_load # # In order to get at the original root object from a class method, we store # its object_id in the Marshal string. # # See SpecialDumpable#_dump # # Note: this means that classes with their own _load and _dump methods # cannot be used with this implementation. I think it should be possible to # enhance the implementation to support these classes, too. # # Using those two methods, the objects restored from Marshal really # reference the original root object. But this is not enough. We have to # restore the instance variables of the root object, too. The _dump method # from above only dumps the object_id of the root object, not its instance # variables. # # For the instance variables, we create a new object and store the instance # variables of the root object there. Then we not only serialize the root # object, but an array with both the root object and the object with the # instance variables. # # See SpecialDumpable#special_dump # # The root object dumps its object_id, and the object with the instance # variables dumps the instance variables of the root object. # # When restoring the objects, Marshal creates an array with the root object # plus an object with the restored instance variables of the root object. # We only need to replace the current instance variables of the root object # with the restored instance variables to get the desired behaviour. # # See SpecialDumpable#special_restore # # That's it. module SpecialDumpable def self.included base base.extend ClassMethods end module ClassMethods def _load source ObjectSpace._id2ref source.to_i end end def _dump limit object_id.to_s end def special_dump value_holder = Object.new SpecialDumpable.copy_instance_variables self, value_holder Marshal.dump [ self, value_holder ] end def special_restore source self_that_can_be_ignored, value_holder = Marshal.restore source SpecialDumpable.copy_instance_variables value_holder, self self end def self.copy_instance_variables from, to from.instance_variables.each do |var| val = from.instance_variable_get var to.instance_variable_set var, val end end end if __FILE__ == $0 class Child attr_accessor :parent end class Parent include SpecialDumpable attr_reader :children def initialize @children = [] end def << child child.parent = self @children << child end end parent = Parent.new puts "parent.object_id: #{parent.object_id}" parent << Child.new puts "parent.children[0].parent.object_id: #{parent.children[0].parent.object_id}" puts "starting transaction with childcount #{parent.children.size}" s = parent.special_dump parent << Child.new puts "parent.children[1].parent.object_id: #{parent.children[1].parent.object_id}" puts "aborting transaction with childcount #{parent.children.size}" parent.special_restore s puts "aborted transaction with childcount #{parent.children.size}" puts "parent.object_id: #{parent.object_id}" puts "parent.children[0].parent.object_id: #{parent.children[0].parent.object_id}" parent << Child.new puts "parent.children[1].parent.object_id: #{parent.children[1].parent.object_id}" end # vim: syntax=ruby transaction-simple-1.4.0.2/research/special-dumpable-string.rb0000755000004100000410000000223212014527406024357 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- load 'special-dumpable.rb' class Child attr_accessor :parent end class Parent < String include SpecialDumpable attr_reader :children def initialize(value) super @children = [] end def << child child.parent = self @children << child end end parent = Parent.new("gold") puts "parent(#{parent}).object_id: #{parent.object_id}" parent << Child.new puts "parent(#{parent}).children[0].parent.object_id: #{parent.children[0].parent.object_id}" puts "starting transaction with childcount #{parent.children.size}" s = parent.special_dump parent << Child.new parent.gsub!(/gold/, 'pyrite') puts "parent(#{parent}).children[1].parent.object_id: #{parent.children[1].parent.object_id}" puts "aborting transaction with childcount #{parent.children.size}" parent.special_restore s puts "parent(#{parent})" puts "aborted transaction with childcount #{parent.children.size}" puts "parent.object_id: #{parent.object_id}" puts "parent.children[0].parent.object_id: #{parent.children[0].parent.object_id}" parent << Child.new puts "parent.children[1].parent.object_id: #{parent.children[1].parent.object_id}" # vim: syntax=ruby transaction-simple-1.4.0.2/History.rdoc0000644000004100000410000000425212014527406020036 0ustar www-datawww-data== 1.4.0.2 / 2012-06-20 * Bookkeeping release that fixes install in scenarios where the RubyGem indicated it had a runtime dependency on hoe. Thanks to Michael Grosser (https://github.com/grosser) for the inspiration. * Marked all files as UTF-8. == 1.4.0.1 / 2007-10-01 * Fixed a simple bug with the #transaction method handling. == 1.4.0 / 2007-02-03 * Adding a post-rewind hook (#_post_transaction_rewind) so that complex graph objects can correct themselves after rewinding. See the documentation for #rewind_transaction and tests/tc_broken_graph.rb for more information. * Removed all warnings. * Deprecated the #transaction method. * Various administrative changes: - Converted to a hoe-driven project. - Made the gem and .tar.gz files idempotent. - Cleaned up the code a little further. - Changed non-gem installer to setup.rb version 3.4.1. == 1.3.1 * Explicitly clearing the transaction checkpoint on objects when the last open transaction is committed or aborted. * Fixed up behaviour to remove a lot of #respond_to? calls. == 1.3.0 * Updated to fix a lot of warnings. * Added a per-transaction-object list of excluded instance variables. * Moved Transaction::simple::ThreadSafe to transaction/simple/threadsafe. * Added transaction groups. Transaction groups are wrapper objects to allow the coordination of transactions with a group of objects. There are both normal and threadsafe versions of transaction groups. * Fixed a long-standing problem where instance variables that were added to an object after a transaction was started would remain. * Reorganised unit tests. == 1.2.0 * Added a RubyGem. * Added a block form of Transaction::Simple. == 1.1.1 * Cleaned up some documentation. == 1.1 * Added Transaction::Simple::ThreadSafe for truly atomic and thread-safe transactions. * Fixed the description of Transaction::Simple to note that it is *not* atomic because it is not implicitly thread-safe. * Added support for named transactions. Named transactions can be used to make checkpoints that can be committed, aborted, or rewound without explicitly committing, aborting, or rewinding the intervening transactions. == 1.0 * Created. Initial release. transaction-simple-1.4.0.2/lib/0000755000004100000410000000000012014527406016267 5ustar www-datawww-datatransaction-simple-1.4.0.2/lib/transaction/0000755000004100000410000000000012014527406020614 5ustar www-datawww-datatransaction-simple-1.4.0.2/lib/transaction/simple.rb0000644000004100000410000004402712014527406022441 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- # :main: README.rdoc # 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 = { #:nodoc: :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 => "Transaction", :opened => "open", :closed => "closed", :transaction_name => "Transaction Name", :start_transaction => "Start Transaction", :rewind_transaction => "Rewind Transaction", :commit_transaction => "Commit Transaction", :abort_transaction => "Abort Transaction", } end module Transaction::Simple VERSION = TRANSACTION_SIMPLE_VERSION = '1.4.0.2' class << self # Sets the Transaction::Simple debug object. It must respond to #<<. # Debugging will be performed automatically if there's a debug object. def debug_io=(io) if io.nil? @tdi = nil @debugging = false else raise Transaction::TransactionError, Transaction::Messages[:bad_debug_object] unless io.respond_to?(:<<) @tdi = io @debugging = true end end # Set to +true+ if you want the checkpoint printed with debugging # messages where it matters. attr_accessor :debug_with_checkpoint # Returns +true+ if we are debugging. def debugging? defined? @debugging and @debugging end # Returns the Transaction::Simple debug object. It must respond to #<<. def debug_io @tdi ||= "" @tdi end # Fast debugging. def debug(format, *args) return unless debugging? debug_io << (format % args) end end def ___tmessage Transaction::Messages end private :___tmessage def ___tdebug(char, format, *args) return unless Transaction::Simple.debugging? if @__transaction_level__ > 0 Transaction::Simple.debug "#{char * @__transaction_level__} #{format}", args else Transaction::Simple.debug "#{format}", args end end private :___tdebug def ___tdebug_checkpoint return unless Transaction::Simple.debugging? return unless Transaction::Simple.debug_with_checkpoint ___tdebug '|', '%s', @__transaction_checkpoint__.inspect end private :___tdebug_checkpoint # 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 known as +name+ open. def transaction_open?(name = nil) defined? @__transaction_checkpoint__ or @__transaction_checkpoint__ = nil has_t = nil if name.nil? has_t = (not @__transaction_checkpoint__.nil?) else has_t = ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name)) end ___tdebug '>', "%s [%s]", ___tmessage[:transaction], ___tmessage[has_t ? :opened : :closed] has_t end # Returns the current name of the transaction. Transactions not explicitly # named are named +nil+. def transaction_name raise Transaction::TransactionError, ___tmessage[:no_transaction_open] if @__transaction_checkpoint__.nil? name = @__transaction_names__.last ___tdebug '|', "%s(%s)", ___tmessage[:transaction_name], name.inspect if name.kind_of?(String) name.dup else name 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__ ||= [] name = name.dup.freeze if name.kind_of?(String) raise Transaction::TransactionError, ___tmessage[:unique_names] if name and @__transaction_names__.include?(name) @__transaction_names__ << name @__transaction_level__ += 1 ___tdebug '>', "%s(%s)", ___tmessage[:start_transaction], name.inspect ___tdebug_checkpoint checkpoint = Marshal.dump(self) @__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. # # After each level of transaction is rewound, if the callback method # #_post_transaction_rewind is defined, it will be called. It is intended # to allow a complex self-referential graph to fix itself. The simplest # way to explain this is with an example. # # class Child # attr_accessor :parent # end # # class Parent # include Transaction::Simple # # attr_reader :children # def initialize # @children = [] # end # # def << child # child.parent = self # @children << child # end # # def valid? # @children.all? { |child| child.parent == self } # end # end # # parent = Parent.new # parent << Child.new # parent.start_transaction # parent << Child.new # parent.abort_transaction # puts parent.valid? # => false # # This problem can be fixed by modifying the Parent class to include the # #_post_transaction_rewind callback. # # class Parent # # Reconnect the restored children to me, instead of to the bogus me # # that was restored to them by Marshal::load. # def _post_transaction_rewind # @children.each { |child| child.parent = self } # end # end # # parent = Parent.new # parent << Child.new # parent.start_transaction # parent << Child.new # parent.abort_transaction # puts parent.valid? # => true def rewind_transaction(name = nil) raise Transaction::TransactionError, ___tmessage[:cannot_rewind_no_transaction] if @__transaction_checkpoint__.nil? # Check to see if we are trying to rewind a transaction that is outside # of the current transaction block. defined? @__transaction_block__ or @__transaction_block__ = nil if @__transaction_block__ and name nix = @__transaction_names__.index(name) + 1 raise Transaction::TransactionError, ___tmessage[:cannot_rewind_transaction_before_block] if nix < @__transaction_block__ end if name.nil? checkpoint = @__transaction_checkpoint__ __rewind_this_transaction @__transaction_checkpoint__ = checkpoint else raise Transaction::TransactionError, ___tmessage[:cannot_rewind_named_transaction] % name.inspect unless @__transaction_names__.include?(name) while @__transaction_names__.last != name ___tdebug_checkpoint @__transaction_checkpoint__ = __rewind_this_transaction ___tdebug '<', ___tmessage[:rewind_transaction], name @__transaction_level__ -= 1 @__transaction_names__.pop end checkpoint = @__transaction_checkpoint__ __rewind_this_transaction @__transaction_checkpoint__ = checkpoint end ___tdebug '|', "%s(%s)", ___tmessage[:rewind_transaction], name.inspect ___tdebug_checkpoint self end # Aborts the transaction. Rewinds 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. # # See #rewind_transaction for information about dealing with complex # self-referential object graphs. # # 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) raise Transaction::TransactionError, ___tmessage[:cannot_abort_no_transaction] if @__transaction_checkpoint__.nil? # 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. defined? @__transaction_block__ or @__transaction_block__ = nil if @__transaction_block__ and name nix = @__transaction_names__.index(name) + 1 raise Transaction::TransactionError, ___tmessage[:cannot_abort_transaction_before_block] if nix < @__transaction_block__ raise Transaction::TransactionAborted if @__transaction_block__ == nix end raise Transaction::TransactionAborted if @__transaction_block__ == @__transaction_level__ if name.nil? __abort_transaction(name) else raise Transaction::TransactionError, ___tmessage[:cannot_abort_named_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 Transaction::TransactionError, ___tmessage[:cannot_commit_no_transaction] if @__transaction_checkpoint__.nil? @__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 raise Transaction::TransactionError, ___tmessage[:cannot_commit_transaction_before_block] if nix < @__transaction_block__ raise Transaction::TransactionCommitted if @__transaction_block__ == nix end raise Transaction::TransactionCommitted if @__transaction_block__ == @__transaction_level__ if name.nil? ___tdebug "<", "%s(%s)", ___tmessage[:commit_transaction], name.inspect __commit_transaction else raise Transaction::TransactionError, ___tmessage[:cannot_commit_named_transaction] % name.inspect unless @__transaction_names__.include?(name) while @__transaction_names__.last != name ___tdebug "<", "%s(%s)", ___tmessage[:commit_transaction], name.inspect __commit_transaction ___tdebug_checkpoint end ___tdebug "<", "%s(%s)", ___tmessage[:commit_transaction], name.inspect __commit_transaction end ___tdebug_checkpoint self end # Alternative method for calling the transaction methods. An optional name # can be specified for named transaction support. This method is # deprecated and will be removed in Transaction::Simple 2.0. # # #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) _method = case action when :start then :start_transaction when :rewind then :rewind_transaction when :abort then :abort_transaction when :commit then :commit_transaction when :name then :transaction_name when nil then :transaction_open? else nil end if _method warn "The #transaction method has been deprecated. Use #{_method} instead." else warn "The #transaction method has been deprecated." end case _method when :transaction_name __send__ _method when nil nil else __send__ _method, 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) raise Transaction::TransactionError, ___tmessage[:cannot_start_empty_block_transaction] if vars.empty? 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 Transaction::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 Transaction::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 # Start a named transaction in a block. The transaction will auto-commit # when the block finishes. def start_named(name, *vars, &block) __common_start(name, vars, &block) end # Start a named transaction in a block. The transaction will auto-commit # when the block finishes. def start(*vars, &block) __common_start(nil, vars, &block) end end def __abort_transaction(name = nil) #:nodoc: @__transaction_checkpoint__ = __rewind_this_transaction ___tdebug '<', "%s(%s)", ___tmessage[:abort_transaction], name.inspect ___tdebug_checkpoint @__transaction_level__ -= 1 @__transaction_names__.pop if @__transaction_level__ < 1 @__transaction_level__ = 0 @__transaction_names__ = [] @__transaction_checkpoint__ = nil end end private :__abort_transaction SKIP_TRANSACTION_VARS = %w(@__transaction_checkpoint__ @__transaction_level__) def __rewind_this_transaction #:nodoc: defined? @__transaction_checkpoint__ or @__transaction_checkpoint__ = nil raise Transaction::TransactionError, ___tmessage[:cannot_rewind_no_transaction] if @__transaction_checkpoint__.nil? rr = Marshal.restore(@__transaction_checkpoint__) replace(rr) if respond_to?(:replace) iv = rr.instance_variables - SKIP_TRANSACTION_VARS - self.transaction_exclusions iv.each do |vv| next if self.transaction_exclusions.include?(vv) instance_variable_set(vv, rr.instance_variable_get(vv)) end rest = instance_variables - rr.instance_variables - SKIP_TRANSACTION_VARS - self.transaction_exclusions rest.each do |vv| remove_instance_variable(vv) end _post_transaction_rewind if respond_to?(:_post_transaction_rewind) w, $-w = $-w, false # 20070203 OH is this very UGLY res = rr.instance_variable_get(:@__transaction_checkpoint__) $-w = w # 20070203 OH is this very UGLY res end private :__rewind_this_transaction def __commit_transaction #:nodoc: defined? @__transaction_checkpoint__ or @__transaction_checkpoint__ = nil raise Transaction::TransactionError, ___tmessage[:cannot_commit_no_transaction] if @__transaction_checkpoint__.nil? old = Marshal.restore(@__transaction_checkpoint__) w, $-w = $-w, false # 20070203 OH is this very UGLY @__transaction_checkpoint__ = old.instance_variable_get(:@__transaction_checkpoint__) $-w = w # 20070203 OH is this very UGLY @__transaction_level__ -= 1 @__transaction_names__.pop if @__transaction_level__ < 1 @__transaction_level__ = 0 @__transaction_names__ = [] @__transaction_checkpoint__ = nil end end private :__commit_transaction end # vim: syntax=ruby transaction-simple-1.4.0.2/lib/transaction/simple/0000755000004100000410000000000012014527406022105 5ustar www-datawww-datatransaction-simple-1.4.0.2/lib/transaction/simple/group.rb0000644000004100000410000001161512014527406023572 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- require 'transaction/simple' # A transaction group is an object wrapper that manages a group of objects # as if they were a single object for the purpose of transaction management. # All transactions for this group of objects should be performed against the # transaction group object, not against individual objects in the group. # # == Transaction Group Usage # require 'transaction/simple/group' # # x = "Hello, you." # y = "And you, too." # # g = Transaction::Simple::Group.new(x, y) # g.start_transaction(:first) # -> [ x, y ] # g.transaction_open?(:first) # -> true # x.transaction_open?(:first) # -> true # y.transaction_open?(:first) # -> true # # x.gsub!(/you/, "world") # -> "Hello, world." # y.gsub!(/you/, "me") # -> "And me, too." # # g.start_transaction(:second) # -> [ x, y ] # x.gsub!(/world/, "HAL") # -> "Hello, HAL." # y.gsub!(/me/, "Dave") # -> "And Dave, too." # g.rewind_transaction(:second) # -> [ x, y ] # x # -> "Hello, world." # y # -> "And me, too." # # x.gsub!(/world/, "HAL") # -> "Hello, HAL." # y.gsub!(/me/, "Dave") # -> "And Dave, too." # # g.commit_transaction(:second) # -> [ x, y ] # x # -> "Hello, HAL." # y # -> "And Dave, too." # # g.abort_transaction(:first) # -> [ x, y ] # x = -> "Hello, you." # y = -> "And you, too." class Transaction::Simple::Group # Creates a transaction group for the provided objects. If a block is # provided, the transaction group object is yielded to the block; when the # block is finished, the transaction group object will be cleared with # #clear. def initialize(*objects) @objects = objects || [] @objects.freeze @objects.each { |obj| obj.extend(Transaction::Simple) } if block_given? begin yield self ensure self.clear end end end # Returns the objects that are covered by this transaction group. attr_reader :objects # Clears the object group. Removes references to the objects so that they # can be garbage collected. def clear @objects = @objects.dup.clear end # Tests to see if all of the objects in the group have an open # transaction. See Transaction::Simple#transaction_open? for more # information. def transaction_open?(name = nil) @objects.inject(true) do |val, obj| val = val and obj.transaction_open?(name) end end # Returns the current name of the transaction for the group. Transactions # not explicitly named are named +nil+. def transaction_name @objects[0].transaction_name end # Starts a transaction for the group. 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) @objects.each { |obj| obj.start_transaction(name) } 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) @objects.each { |obj| obj.rewind_transaction(name) } 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) @objects.each { |obj| obj.abort_transaction(name) } 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) @objects.each { |obj| obj.commit_transaction(name) } 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) @objects.each { |obj| obj.transaction(action, name) } end end # vim: syntax=ruby transaction-simple-1.4.0.2/lib/transaction/simple/threadsafe/0000755000004100000410000000000012014527406024213 5ustar www-datawww-datatransaction-simple-1.4.0.2/lib/transaction/simple/threadsafe/group.rb0000644000004100000410000000142212014527406025673 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- require 'transaction/simple/threadsafe' # A transaction group is an object wrapper that manages a group of objects # as if they were a single object for the purpose of transaction management. # All transactions for this group of objects should be performed against the # transaction group object, not against individual objects in the group. # This is the threadsafe version of a transaction group. class Transaction::Simple::ThreadSafe::Group < Transaction::Simple::Group def initialize(*objects) @objects = objects || [] @objects.freeze @objects.each { |obj| obj.extend(Transaction::Simple::ThreadSafe) } if block_given? begin yield self ensure self.clear end end end end # vim: syntax=ruby transaction-simple-1.4.0.2/lib/transaction/simple/threadsafe.rb0000644000004100000410000000346012014527406024543 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- require 'transaction/simple' require 'thread' module Transaction # A standard exception for transaction errors involving threadsafe # transactions. class TransactionThreadError < TransactionError; end end # 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 Fernandez for help with getting this part working. # # Threadsafe transactions can be used in any place that normal transactions # would. The main difference would be in setup: # # require 'transaction/simple/threadsafe' # # x = "Hello, you." # x.extend(Transaction::Simple::ThreadSafe) # Threadsafe # # y = "Hello, you." # y.extend(Transaction::Simple) # Not threadsafe module Transaction::Simple::ThreadSafe 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, Messages[:cannot_obtain_transaction_lock] % meth end ensure @__transaction_mutex__.unlock end EOS end end # vim: syntax=ruby transaction-simple-1.4.0.2/lib/transaction-simple.rb0000644000004100000410000000012112014527406022422 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- require 'transaction/simple' # vim: syntax=ruby