Module Transaction::Simple
In: lib/transaction/simple.rb
TransactionError TransactionThreadError Exception TransactionCommitted TransactionAborted StandardError Group Group lib/transaction/simple/threadsafe.rb lib/transaction/simple.rb lib/transaction/simple/group.rb lib/transaction/simple/threadsafe/group.rb ThreadSafe Simple Transaction dot/m_3_0.png

Transaction::Simple for Ruby

Simple object transaction support for Ruby

Methods

Classes and Modules

Module Transaction::Simple::ThreadSafe
Class Transaction::Simple::Group

Constants

TRANSACTION_SIMPLE_VERSION = '1.4.0'
SKIP_TRANSACTION_VARS = %w(@__transaction_checkpoint__ @__transaction_level__)

Public Class methods

Returns the Transaction::Simple debug object. It must respond to #<<.

[Source]

    # File lib/transaction/simple.rb, line 75
75:     def debug_io
76:       @tdi ||= ""
77:       @tdi
78:     end

Sets the Transaction::Simple debug object. It must respond to #<<. Debugging will be performed automatically if there‘s a debug object.

[Source]

    # File lib/transaction/simple.rb, line 58
58:     def debug_io=(io)
59:       if io.nil?
60:         @tdi        = nil
61:         @debugging  = false
62:       else
63:         raise Transaction::TransactionError, Transaction::Messages[:bad_debug_object] unless io.respond_to?(:<<)
64:         @tdi = io
65:         @debugging = true
66:       end
67:     end

Returns true if we are debugging.

[Source]

    # File lib/transaction/simple.rb, line 70
70:     def debugging?
71:       defined? @debugging and @debugging
72:     end

Start a named transaction in a block. The transaction will auto-commit when the block finishes.

[Source]

     # File lib/transaction/simple.rb, line 409
409:     def start(*vars, &block)
410:       __common_start(nil, vars, &block)
411:     end

Start a named transaction in a block. The transaction will auto-commit when the block finishes.

[Source]

     # File lib/transaction/simple.rb, line 403
403:     def start_named(name, *vars, &block)
404:       __common_start(name, vars, &block)
405:     end

Private Class methods

[Source]

     # File lib/transaction/simple.rb, line 346
346:     def __common_start(name, vars, &block)
347:       raise Transaction::TransactionError, Transaction::Messages[:cannot_start_empty_block_transaction] if vars.empty?
348: 
349:       if block
350:         begin
351:           vlevel = {}
352: 
353:           vars.each do |vv|
354:             vv.extend(Transaction::Simple)
355:             vv.start_transaction(name)
356:             vlevel[vv.__id__] = vv.instance_variable_get(:@__transaction_level__)
357:             vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__])
358:           end
359: 
360:           yield(*vars)
361:         rescue Transaction::TransactionAborted
362:           vars.each do |vv|
363:             if name.nil? and vv.transaction_open?
364:               loop do
365:                 tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
366:                 vv.instance_variable_set(:@__transaction_block__, -1)
367:                 break if tlevel < vlevel[vv.__id__]
368:                 vv.abort_transaction if vv.transaction_open?
369:               end
370:             elsif vv.transaction_open?(name)
371:               vv.instance_variable_set(:@__transaction_block__, -1)
372:               vv.abort_transaction(name)
373:             end
374:           end
375:         rescue Transaction::TransactionCommitted
376:           nil
377:         ensure
378:           vars.each do |vv|
379:             if name.nil? and vv.transaction_open?
380:               loop do
381:                 tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
382:                 break if tlevel < vlevel[vv.__id__]
383:                 vv.instance_variable_set(:@__transaction_block__, -1)
384:                 vv.commit_transaction if vv.transaction_open?
385:               end
386:             elsif vv.transaction_open?(name)
387:               vv.instance_variable_set(:@__transaction_block__, -1)
388:               vv.commit_transaction(name)
389:             end
390:           end
391:         end
392:       else
393:         vars.each do |vv|
394:           vv.extend(Transaction::Simple)
395:           vv.start_transaction(name)
396:         end
397:       end
398:     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.

[Source]

     # File lib/transaction/simple.rb, line 232
232:   def abort_transaction(name = nil)
233:     raise Transaction::TransactionError, Transaction::Messages[:cannot_abort_no_transaction] if @__transaction_checkpoint__.nil?
234: 
235:     # Check to see if we are trying to abort a transaction that is outside
236:     # of the current transaction block. Otherwise, raise TransactionAborted
237:     # if they are the same.
238:     defined? @__transaction_block__ or @__transaction_block__ = nil
239:     if @__transaction_block__ and name
240:       nix = @__transaction_names__.index(name) + 1
241:       raise Transaction::TransactionError, Transaction::Messages[:cannot_abort_transaction_before_block] if nix < @__transaction_block__
242: 
243:       raise Transaction::TransactionAborted if @__transaction_block__ == nix
244:     end
245: 
246:     raise Transaction::TransactionAborted if @__transaction_block__ == @__transaction_level__
247: 
248:     if name.nil?
249:       __abort_transaction(name)
250:     else
251:       raise Transaction::TransactionError, Transaction::Messages[:cannot_abort_named_transaction] % name.inspect unless @__transaction_names__.include?(name)
252:       __abort_transaction(name) while @__transaction_names__.include?(name)
253:     end
254: 
255:     self
256:   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.

[Source]

     # File lib/transaction/simple.rb, line 264
264:   def commit_transaction(name = nil)
265:     raise Transaction::TransactionError, Transaction::Messages[:cannot_commit_no_transaction] if @__transaction_checkpoint__.nil?
266:     @__transaction_block__ ||= nil
267: 
268:     # Check to see if we are trying to commit a transaction that is outside
269:     # of the current transaction block. Otherwise, raise
270:     # TransactionCommitted if they are the same.
271:     if @__transaction_block__ and name
272:       nix = @__transaction_names__.index(name) + 1
273:       raise Transaction::TransactionError, Transaction::Messages[:cannot_commit_transaction_before_block] if nix < @__transaction_block__
274: 
275:       raise Transaction::TransactionCommitted if @__transaction_block__ == nix
276:     end
277: 
278:     raise Transaction::TransactionCommitted if @__transaction_block__ == @__transaction_level__
279: 
280:     if name.nil?
281:       ss = "" if Transaction::Simple.debugging?
282:       __commit_transaction
283:       Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << "Commit Transaction#{ss}\n" if Transaction::Simple.debugging?
284:     else
285:       raise Transaction::TransactionError, Transaction::Messages[:cannot_commit_named_transaction] % name.inspect unless @__transaction_names__.include?(name)
286:       ss = "(#{name})" if Transaction::Simple.debugging?
287: 
288:       while @__transaction_names__[-1] != name
289:         Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << "Commit Transaction#{ss}\n" if Transaction::Simple.debugging?
290:         __commit_transaction
291:       end
292:       Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << "Commit Transaction#{ss}\n" if Transaction::Simple.debugging?
293:       __commit_transaction
294:     end
295: 
296:     self
297:   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

[Source]

     # File lib/transaction/simple.rb, line 187
187:   def rewind_transaction(name = nil)
188:     raise Transaction::TransactionError, Transaction::Messages[:cannot_rewind_no_transaction] if @__transaction_checkpoint__.nil?
189: 
190:     # Check to see if we are trying to rewind a transaction that is
191:     # outside of the current transaction block.
192:     defined? @__transaction_block__ or @__transaction_block__ = nil
193:     if @__transaction_block__ and name
194:       nix = @__transaction_names__.index(name) + 1
195:       raise Transaction::TransactionError, Transaction::Messages[:cannot_rewind_transaction_before_block] if nix < @__transaction_block__
196:     end
197: 
198:     if name.nil?
199:       checkpoint = @__transaction_checkpoint__
200:       __rewind_this_transaction
201:       @__transaction_checkpoint__ = checkpoint
202:       ss = "" if Transaction::Simple.debugging?
203:     else
204:       raise Transaction::TransactionError, Transaction::Messages[:cannot_rewind_named_transaction] % name.inspect unless @__transaction_names__.include?(name)
205:       ss = "(#{name})" if Transaction::Simple.debugging?
206: 
207:       while @__transaction_names__[-1] != name
208:         @__transaction_checkpoint__ = __rewind_this_transaction
209:         Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << "Rewind Transaction#{ss}\n" if Transaction::Simple.debugging?
210:         @__transaction_level__ -= 1
211:         @__transaction_names__.pop
212:       end
213:       checkpoint = @__transaction_checkpoint__
214:       __rewind_this_transaction
215:       @__transaction_checkpoint__ = checkpoint
216:     end
217:     Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << "Rewind Transaction#{ss}\n" if Transaction::Simple.debugging?
218:     self
219:   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.

[Source]

     # File lib/transaction/simple.rb, line 111
111:   def start_transaction(name = nil)
112:     @__transaction_level__ ||= 0
113:     @__transaction_names__ ||= []
114: 
115:     name = name.dup.freeze if name.kind_of?(String)
116: 
117:     raise Transaction::TransactionError, Transaction::Messages[:unique_names] if name and @__transaction_names__.include?(name)
118: 
119:     @__transaction_names__ << name
120:     @__transaction_level__ += 1
121: 
122:     if Transaction::Simple.debugging?
123:       ss = "(#{name.inspect})"
124:       ss = "" unless ss
125: 
126:       Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} " << "Start Transaction#{ss}\n"
127:     end
128: 
129:     @__transaction_checkpoint__ = Marshal.dump(self)
130:   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?

[Source]

     # File lib/transaction/simple.rb, line 309
309:   def transaction(action = nil, name = nil)
310:     _method = case action
311:               when :start then :start_transaction
312:               when :rewind then :rewind_transaction
313:               when :abort then :abort_transaction
314:               when :commit then :commit_transaction
315:               when :name then :transaction_name
316:               when nil then :transaction_open?
317:               else nil
318:               end
319: 
320:     if method
321:       warn "The #transaction method has been deprecated. Use #{method} instead."
322:     else
323:       warn "The #transaction method has been deprecated."
324:     end
325: 
326:     case method
327:     when :transaction_name
328:       __send__ method
329:     when nil
330:       nil
331:     else
332:       __send__ method, name
333:     end
334:   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"

[Source]

     # File lib/transaction/simple.rb, line 341
341:   def transaction_exclusions
342:     @transaction_exclusions ||= []
343:   end

Returns the current name of the transaction. Transactions not explicitly named are named nil.

[Source]

     # File lib/transaction/simple.rb, line 97
 97:   def transaction_name
 98:     raise Transaction::TransactionError, Transaction::Messages[:no_transaction_open] if @__transaction_checkpoint__.nil?
 99:     Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << "Transaction Name: #{@__transaction_names__[-1].inspect}\n" if Transaction::Simple.debugging?
100:     if @__transaction_names__[-1].kind_of?(String)
101:       @__transaction_names__[-1].dup
102:     else
103:       @__transaction_names__[-1]
104:     end
105:   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.

[Source]

    # File lib/transaction/simple.rb, line 84
84:   def transaction_open?(name = nil)
85:     defined? @__transaction_checkpoint__ or @__transaction_checkpoint__ = nil
86:     if name.nil?
87:       Transaction::Simple.debug_io << "Transaction " << "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" if Transaction::Simple.debugging?
88:       return (not @__transaction_checkpoint__.nil?)
89:     else
90:       Transaction::Simple.debug_io << "Transaction(#{name.inspect}) " << "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" if Transaction::Simple.debugging?
91:       return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
92:     end
93:   end

[Validate]