Module | Transaction::Simple
In: | lib/active_record/vendor/simple.rb

# Transaction::Simple for Ruby

Simple object transaction support for Ruby

## Introduction

Transaction::Simple provides a generic way to add active transaction support to objects. The transaction methods added by this module will work with most objects, excluding those that cannot be Marshaled (bindings, procedure objects, IO instances, or singleton objects).

The transactions supported by Transaction::Simple are not backed transactions; they are not associated with any sort of data store. They are "live" transactions occurring in memory and in the object itself. This is to allow "test" changes to be made to an object before making the changes permanent.

Transaction::Simple can handle an "infinite" number of transaction levels (limited only by memory). If I open two transactions, commit the second, but abort the first, the object will revert to the original version.

Transaction::Simple supports "named" transactions, so that multiple levels of transactions can be committed, aborted, or rewound by referring to the appropriate name of the transaction. Names may be any object except `nil`. As with Hash keys, String names will be duplicated and frozen before using.

Copyright: | Copyright © 2003 - 2005 by Austin Ziegler
Version: | 1.3.0
Licence: | MIT-Style

Thanks to David Black for help with the initial concept that led to this library.

## Usage

      include 'transaction/simple'

      v = "Hello, you."               # -> "Hello, you."
      v.extend(Transaction::Simple)   # -> "Hello, you."

      v.start_transaction             # -> ... (a Marshal string)
      v.transaction_open?             # -> true
      v.gsub!(/you/, "world")         # -> "Hello, world."

      v.rewind_transaction            # -> "Hello, you."
      v.transaction_open?             # -> true

      v.gsub!(/you/, "HAL")           # -> "Hello, HAL."
      v.abort_transaction             # -> "Hello, you."
      v.transaction_open?             # -> false

      v.start_transaction             # -> ... (a Marshal string)
      v.start_transaction             # -> ... (a Marshal string)

      v.transaction_open?             # -> true
      v.gsub!(/you/, "HAL")           # -> "Hello, HAL."

      v.commit_transaction            # -> "Hello, HAL."
      v.transaction_open?             # -> true
      v.abort_transaction             # -> "Hello, you."
      v.transaction_open?             # -> false

## Named Transaction Usage

      v = "Hello, you."               # -> "Hello, you."
      v.extend(Transaction::Simple)   # -> "Hello, you."

      v.start_transaction(:first)     # -> ... (a Marshal string)
      v.transaction_open?             # -> true
      v.transaction_open?(:first)     # -> true
      v.transaction_open?(:second)    # -> false
      v.gsub!(/you/, "world")         # -> "Hello, world."

      v.start_transaction(:second)    # -> ... (a Marshal string)
      v.gsub!(/world/, "HAL")         # -> "Hello, HAL."
      v.rewind_transaction(:first)    # -> "Hello, you."
      v.transaction_open?             # -> true
      v.transaction_open?(:first)     # -> true
      v.transaction_open?(:second)    # -> false

      v.gsub!(/you/, "world")         # -> "Hello, world."
      v.start_transaction(:second)    # -> ... (a Marshal string)
      v.gsub!(/world/, "HAL")         # -> "Hello, HAL."
      v.transaction_name              # -> :second
      v.abort_transaction(:first)     # -> "Hello, you."
      v.transaction_open?             # -> false

      v.start_transaction(:first)     # -> ... (a Marshal string)
      v.gsub!(/you/, "world")         # -> "Hello, world."
      v.start_transaction(:second)    # -> ... (a Marshal string)
      v.gsub!(/world/, "HAL")         # -> "Hello, HAL."

      v.commit_transaction(:first)    # -> "Hello, HAL."
      v.transaction_open?             # -> false

## Block Usage

      v = "Hello, you."               # -> "Hello, you."
      Transaction::Simple.start(v) do |tv|
          # v has been extended with Transaction::Simple and an unnamed
          # transaction has been started.
        tv.transaction_open?          # -> true
        tv.gsub!(/you/, "world")      # -> "Hello, world."

        tv.rewind_transaction         # -> "Hello, you."
        tv.transaction_open?          # -> true

        tv.gsub!(/you/, "HAL")        # -> "Hello, HAL."
          # The following breaks out of the transaction block after
          # aborting the transaction.
        tv.abort_transaction          # -> "Hello, you."
      end
        # v still has Transaction::Simple applied from here on out.
      v.transaction_open?             # -> false

      Transaction::Simple.start(v) do |tv|
        tv.start_transaction          # -> ... (a Marshal string)

        tv.transaction_open?          # -> true
        tv.gsub!(/you/, "HAL")        # -> "Hello, HAL."

          # If #commit_transaction were called without having started a
          # second transaction, then it would break out of the transaction
          # block after committing the transaction.
        tv.commit_transaction         # -> "Hello, HAL."
        tv.transaction_open?          # -> true
        tv.abort_transaction          # -> "Hello, you."
      end
      v.transaction_open?             # -> false

## Named Transaction Usage

      v = "Hello, you."               # -> "Hello, you."
      v.extend(Transaction::Simple)   # -> "Hello, you."

      v.start_transaction(:first)     # -> ... (a Marshal string)
      v.transaction_open?             # -> true
      v.transaction_open?(:first)     # -> true
      v.transaction_open?(:second)    # -> false
      v.gsub!(/you/, "world")         # -> "Hello, world."

      v.start_transaction(:second)    # -> ... (a Marshal string)
      v.gsub!(/world/, "HAL")         # -> "Hello, HAL."
      v.rewind_transaction(:first)    # -> "Hello, you."
      v.transaction_open?             # -> true
      v.transaction_open?(:first)     # -> true
      v.transaction_open?(:second)    # -> false

      v.gsub!(/you/, "world")         # -> "Hello, world."
      v.start_transaction(:second)    # -> ... (a Marshal string)
      v.gsub!(/world/, "HAL")         # -> "Hello, HAL."
      v.transaction_name              # -> :second
      v.abort_transaction(:first)     # -> "Hello, you."
      v.transaction_open?             # -> false

      v.start_transaction(:first)     # -> ... (a Marshal string)
      v.gsub!(/you/, "world")         # -> "Hello, world."
      v.start_transaction(:second)    # -> ... (a Marshal string)
      v.gsub!(/world/, "HAL")         # -> "Hello, HAL."

      v.commit_transaction(:first)    # -> "Hello, HAL."
      v.transaction_open?             # -> false

## Thread Safety

Threadsafe version of Transaction::Simple and Transaction::Simple::Group exist; these are loaded from ‘transaction/simple/threadsafe’ and ‘transaction/simple/threadsafe/group’, respectively, and are represented in Ruby code as Transaction::Simple::ThreadSafe and Transaction::Simple::ThreadSafe::Group, respectively.

## Contraindications

While Transaction::Simple is very useful, it has some severe limitations that must be understood. Transaction::Simple:

- uses Marshal. Thus, any object which cannot be Marshaled cannot use Transaction::Simple. In my experience, this affects singleton objects more often than any other object. It may be that Ruby 2.0 will solve this problem.
- does not manage resources. Resources external to the object and its instance variables are not managed at all. However, all instance variables and objects "belonging" to those instance variables are managed. If there are object reference counts to be handled, Transaction::Simple will probably cause problems.
- is not inherently thread-safe. In the ACID ("atomic, consistent, isolated, durable") test, Transaction::Simple provides CD, but it is up to the user of Transaction::Simple to provide isolation and atomicity. Transactions should be considered "critical sections" in multi-threaded applications. If thread safety and atomicity is absolutely required, use Transaction::Simple::ThreadSafe, which uses a Mutex object to synchronize the accesses on the object during the transaction operations.
- does not necessarily maintain Object#id values on rewind or abort. This may change for future versions that will be Ruby 1.8 or better only. Certain objects that support replace will maintain Object#id.
- Can be a memory hog if you use many levels of transactions on many objects.

### Methods

abort_transaction commit_transaction debug_io debug_io= debugging? rewind_transaction start start_named start_transaction transaction transaction_exclusions transaction_name transaction_open?

### Constants

TRANSACTION_SIMPLE_VERSION | = | '1.3.0'

### Public Class methods

debug_io()

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

debug_io=(io)

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

debugging?()

Returns `true` if we are debugging.

start(*vars, &block)
start_named(name, *vars, &block)

### Public Instance methods

abort_transaction(name = nil)

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`.

commit_transaction(name = nil)

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.

rewind_transaction(name = nil)

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.

start_transaction(name = nil)

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.

transaction(action = nil, name = nil)

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?

transaction_exclusions()

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"

transaction_name()

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

transaction_open?(name = nil)

If `name` is `nil` (default), then returns `true` if there is currently a transaction open.

If `name` is specified, then returns `true` if there is currently a transaction that responds to `name` open.

[Validate]