[ANN] Transaction::Simple 1.3.0

A

Austin Ziegler

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
 
J

Jeff Barczewski

Could one use this as a simple way to provide dialog state across
discrete web pages in a wizard type scenario? Like what some people
try to do with continuations, although this seems to get complicated
quickly. Maybe this could provide an easier mechanism??

For instance if you can start a transaction when you come into the
wizard, commiting and rolling back inner transactions as you change
pages, then finally commiting the final wrapper transaction when you
complete the wizard and then use the data.

One would have to store this somewhere that it could be retrieved by
any thread, but it seems like maybe this might work with a little
thought.

Jeff
 
A

Austin Ziegler

Could one use this as a simple way to provide dialog state across
discrete web pages in a wizard type scenario? Like what some
people try to do with continuations, although this seems to get
complicated quickly. Maybe this could provide an easier
mechanism??

Mmmm. Maybe, but it would depend on the architecture of your program
more than anything. In a stateless situation, then really not. In a
stateful situation, then easily.

These items are for live, in-memory transactions. As long as your
program is running, you should be okay with this.

-austin
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,226
Members
46,815
Latest member
treekmostly22

Latest Threads

Top