I am pleased to announce the release of Transaction::Simple 1.3.0.

It can be downloaded from the trans-simple RubyForge project:

  http://rubyforge.org/frs/?group_id=295&release_id=2130

It may also be installed as a RubyGem.

= Transaction::Simple for Ruby
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 backend
transaction; that is, 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.

Version 1.3.0 of Transaction::Simple adds transaction groups. 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.

Copyright:    Copyright (c) 2003 - 2005 by Austin Ziegler
Version:      1.3.0
Licence:      MIT-Style

Thanks to David Black and Mauricio Fern?ndez for their help with 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 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 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 Marshal-ed 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 thread-safe. In the ACID ("atomic, consistent, isolated,
  durable") test, Transaction::Simple provides C and D, but it is up to
  the user of Transaction::Simple to provide isolation. Transactions
  should be considered "critical sections" in multi-threaded
  applications. Thread safety can be ensured with
  Transaction::Simple::ThreadSafe. With transaction groups, some level
  of atomicity is assured.
* does not maintain Object#__id__ values on rewind or abort. This may
  change for future versions.

== Transaction::simple 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.

-austin
-- 
Austin Ziegler * halostatue / gmail.com
               * Alternate: austin / halostatue.ca