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