module Transaction::Simple
Constants
- SKIP_TRANSACTION_VARS
- VERSION
Attributes
Set to true
if you want the checkpoint printed with debugging
messages where it matters.
Public Class Methods
Fast debugging.
# File lib/transaction/simple.rb, line 77 def debug(format, *args) return unless debugging? debug_io << (format % args) end
Returns the Transaction::Simple debug object. It must respond to <<.
# File lib/transaction/simple.rb, line 71 def debug_io @tdi ||= "" @tdi end
Sets the Transaction::Simple debug object. It must respond to <<. Debugging will be performed automatically if there's a debug object.
# File lib/transaction/simple.rb, line 50 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
Returns true
if we are debugging.
# File lib/transaction/simple.rb, line 66 def debugging? defined? @debugging and @debugging end
Start a named transaction in a block. The transaction will auto-commit when the block finishes.
# File lib/transaction/simple.rb, line 446 def start(*vars, &block) __common_start(nil, vars, &block) end
Start a named transaction in a block. The transaction will auto-commit when the block finishes.
# File lib/transaction/simple.rb, line 440 def start_named(name, *vars, &block) __common_start(name, vars, &block) end
Private Class Methods
# File lib/transaction/simple.rb, line 383 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
Public Instance Methods
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
.
# File lib/transaction/simple.rb, line 268 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.
# File lib/transaction/simple.rb, line 299 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
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
# File lib/transaction/simple.rb, line 220 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
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.
# File lib/transaction/simple.rb, line 146 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
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)
- transaction(:rewind)
- transaction(:abort)
- transaction(:commit)
- transaction(:name)
- transaction
# File lib/transaction/simple.rb, line 346 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"
# File lib/transaction/simple.rb, line 378 def transaction_exclusions @transaction_exclusions ||= [] end
Returns the current name of the transaction. Transactions not explicitly
named are named nil
.
# File lib/transaction/simple.rb, line 128 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
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.
# File lib/transaction/simple.rb, line 109 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
Private Instance Methods
# File lib/transaction/simple.rb, line 88 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
# File lib/transaction/simple.rb, line 98 def ___tdebug_checkpoint return unless Transaction::Simple.debugging? return unless Transaction::Simple.debug_with_checkpoint ___tdebug '|', '%s', @__transaction_checkpoint__.inspect end
# File lib/transaction/simple.rb, line 83 def ___tmessage Transaction::Messages end