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