Transaction::Simple for Ruby

home

trans-simple.rubyforge.org/

code

github.com/halostatue/transaction-simple

bugs

github.com/halostatue/transaction-simple/issues

rdoc

trans-simple.rubyforge.org/

Description

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 associated with any sort of data store. They are “live” transactions occurring in memory on 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.

Transaction groups are also supported. 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.

Version 1.4.0 of Transaction::Simple adds a new post-rewind hook so that complex graph objects of the type in tests/tc_broken_graph.rb can correct themselves.

Version 1.4.0.1 just fixes a simple bug with transaction method handling during the deprecation warning.

Version 1.4.0.2 is a small update for people who use Transaction::Simple in bundler (adding lib/transaction-simple.rb) and other scenarios where having Hoe as a runtime dependency (a bug fixed in Hoe several years ago, but not visible in Transaction::Simple because it has not needed a re-release). All of the files internally have also been marked as UTF-8, ensuring full Ruby 1.9 compatibility.

Usage

require '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!(%ryou/, "world")         # -> "Hello, world."

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

v.gsub!(%ryou/, "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!(%ryou/, "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!(%ryou/, "world")      # -> "Hello, world."

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

  tv.gsub!(%ryou/, "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!(%ryou/, "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!(%ryou/, "world")         # -> "Hello, world."
y.gsub!(%ryou/, "me")            # -> "And me, too."

g.start_transaction(:second)    # -> [ x, y ]
x.gsub!(%rworld/, "HAL")         # -> "Hello, HAL."
y.gsub!(%rme/, "Dave")           # -> "And Dave, too."

g.rewind_transaction(:second)   # -> [ x, y ]
x                               # -> "Hello, world."
y                               # -> "And me, too."

x.gsub!(%rworld/, "HAL")         # -> "Hello, HAL."
y.gsub!(%rme/, "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 versions 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 limitations that must be understood prior to using it. Transaction::Simple:

Licence

This software is available under the terms of the MIT license.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.