transaction-simple 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (9) hide show
  1. data/ChangeLog +20 -0
  2. data/Changelog +20 -0
  3. data/Install +2 -0
  4. data/README +110 -0
  5. data/Rakefile +120 -0
  6. data/Readme +110 -0
  7. data/lib/transaction/simple.rb +591 -0
  8. data/tests/tests.rb +402 -0
  9. metadata +55 -0
@@ -0,0 +1,20 @@
1
+ $Id: Changelog,v 1.2 2004/09/14 18:46:15 austin Exp $
2
+
3
+ == Transaction::Simple 1.2.0
4
+ * Added a RubyGem.
5
+ * Added a block form of Transaction::Simple.
6
+
7
+ == Transaction::Simple 1.1.1
8
+ * Cleaned up some documentation.
9
+
10
+ == Transaction::Simple 1.1
11
+ * Added Transaction::Simple::ThreadSafe for truly atomic and thread-safe
12
+ transactions.
13
+ * Fixed the description of Transaction::Simple to note that it is *not* atomic
14
+ because it is not necessarily thread-safe.
15
+ * Added support for named transactions. Named transactions can be used to make
16
+ checkpoints that can be committed, aborted, or rewound without explicitly
17
+ committing, aborting, or rewinding the intervening transactions.
18
+
19
+ == Transaction::Simple 1.0
20
+ * Created. Initial release.
@@ -0,0 +1,20 @@
1
+ $Id: Changelog,v 1.2 2004/09/14 18:46:15 austin Exp $
2
+
3
+ == Transaction::Simple 1.2.0
4
+ * Added a RubyGem.
5
+ * Added a block form of Transaction::Simple.
6
+
7
+ == Transaction::Simple 1.1.1
8
+ * Cleaned up some documentation.
9
+
10
+ == Transaction::Simple 1.1
11
+ * Added Transaction::Simple::ThreadSafe for truly atomic and thread-safe
12
+ transactions.
13
+ * Fixed the description of Transaction::Simple to note that it is *not* atomic
14
+ because it is not necessarily thread-safe.
15
+ * Added support for named transactions. Named transactions can be used to make
16
+ checkpoints that can be committed, aborted, or rewound without explicitly
17
+ committing, aborting, or rewinding the intervening transactions.
18
+
19
+ == Transaction::Simple 1.0
20
+ * Created. Initial release.
data/Install ADDED
@@ -0,0 +1,2 @@
1
+ Simply run:
2
+ % ruby install.rb
data/README ADDED
@@ -0,0 +1,110 @@
1
+ Transaction::Simple for Ruby
2
+ Simple object transaction support for Ruby
3
+
4
+ Introduction
5
+ ------------
6
+ Transaction::Simple provides a generic way to add active transactional support
7
+ to objects. The transaction methods added by this module will work with most
8
+ objects, excluding those that cannot be Marshal-ed (bindings, procedure
9
+ objects, IO instances, or singleton objects).
10
+
11
+ The transactions supported by Transaction::Simple are not backend transaction;
12
+ that is, they have nothing to do with any sort of data store. They are "live"
13
+ transactions occurring in memory and in the object itself. This is to allow
14
+ "test" changes to be made to an object before making the changes permanent.
15
+
16
+ Transaction::Simple can handle an "infinite" number of transactional levels
17
+ (limited only by memory). If I open two transactions, commit the first, but
18
+ abort the second, the object will revert to the original version.
19
+
20
+ Transaction::Simple supports "named" transactions, so that multiple levels of
21
+ transactions can be committed, aborted, or rewound by referring to the
22
+ appropriate name of the transaction. Names may be any object except nil.
23
+
24
+ Copyright: Copyright � 2003 by Austin Ziegler
25
+ Version: 1.11
26
+ Licence: MIT-Style
27
+
28
+ Thanks to David Black and Mauricio Fern�ndez for their help with this library.
29
+
30
+ Usage
31
+ -----
32
+ include 'transaction/simple'
33
+
34
+ v = "Hello, you." # => "Hello, you."
35
+ v.extend(Transaction::Simple) # => "Hello, you."
36
+
37
+ v.start_transaction # => ... (a Marshal string)
38
+ v.transaction_open? # => true
39
+ v.gsub!(/you/, "world") # => "Hello, world."
40
+
41
+ v.rewind_transaction # => "Hello, you."
42
+ v.transaction_open? # => true
43
+
44
+ v.gsub!(/you/, "HAL") # => "Hello, HAL."
45
+ v.abort_transaction # => "Hello, you."
46
+ v.transaction_open? # => false
47
+
48
+ v.start_transaction # => ... (a Marshal string)
49
+ v.start_transaction # => ... (a Marshal string)
50
+
51
+ v.transaction_open? # => true
52
+ v.gsub!(/you/, "HAL") # => "Hello, HAL."
53
+
54
+ v.commit_transaction # => "Hello, HAL."
55
+ v.transaction_open? # => true
56
+ v.abort_transaction # => "Hello, you."
57
+ v.transaction_open? # => false
58
+
59
+ Named Transaction Usage
60
+ -----------------------
61
+ v = "Hello, you." # => "Hello, you."
62
+ v.extend(Transaction::Simple) # => "Hello, you."
63
+
64
+ v.start_transaction(:first) # => ... (a Marshal string)
65
+ v.transaction_open? # => true
66
+ v.transaction_open?(:first) # => true
67
+ v.transaction_open?(:second) # => false
68
+ v.gsub!(/you/, "world") # => "Hello, world."
69
+
70
+ v.start_transaction(:second) # => ... (a Marshal string)
71
+ v.gsub!(/world/, "HAL") # => "Hello, HAL."
72
+ v.rewind_transaction(:first) # => "Hello, you."
73
+ v.transaction_open? # => true
74
+ v.transaction_open?(:first) # => true
75
+ v.transaction_open?(:second) # => false
76
+
77
+ v.gsub!(/you/, "world") # => "Hello, world."
78
+ v.start_transaction(:second) # => ... (a Marshal string)
79
+ v.gsub!(/world/, "HAL") # => "Hello, HAL."
80
+ v.transaction_name # => :second
81
+ v.abort_transaction(:first) # => "Hello, you."
82
+ v.transaction_open? # => false
83
+
84
+ v.start_transaction(:first) # => ... (a Marshal string)
85
+ v.gsub!(/you/, "world") # => "Hello, world."
86
+ v.start_transaction(:second) # => ... (a Marshal string)
87
+ v.gsub!(/world/, "HAL") # => "Hello, HAL."
88
+
89
+ v.commit_transaction(:first) # => "Hello, HAL."
90
+ v.transaction_open? # => false
91
+
92
+ Contraindications
93
+ -----------------
94
+ While Transaction::Simple is very useful, it has some severe limitations that
95
+ must be understood. Transaction::Simple:
96
+
97
+ * uses Marshal. Thus, any object which cannot be Marshal-ed cannot use
98
+ Transaction::Simple.
99
+ * does not manage resources. Resources external to the object and its instance
100
+ variables are not managed at all. However, all instance variables and
101
+ objects "belonging" to those instance variables are managed. If there are
102
+ object reference counts to be handled, Transaction::Simple will probably
103
+ cause problems.
104
+ * is not thread-safe. In the ACID ("atomic, consistent, isolated, durable")
105
+ test, Transaction::Simple provides C and D, but it is up to the user of
106
+ Transaction::Simple to provide isolation. Transactions should be considered
107
+ "critical sections" in multi-threaded applications. Thread safety can be
108
+ ensured with Transaction::Simple::ThreadSafe.
109
+ * does not maintain Object#__id__ values on rewind or abort. This may change
110
+ for future versions that will be Ruby 1.8 or better only.
@@ -0,0 +1,120 @@
1
+ #! /usr/bin/env rake
2
+ $LOAD_PATH.unshift('lib')
3
+
4
+ require 'rubygems'
5
+ require 'rake/gempackagetask'
6
+ require 'rake/contrib/rubyforgepublisher'
7
+ require 'transaction/simple'
8
+ require 'archive/tar/minitar'
9
+ require 'zlib'
10
+
11
+ DISTDIR = "transaction-simple-#{Transaction::Simple::VERSION}"
12
+ TARDIST = "../#{DISTDIR}.tar.gz"
13
+
14
+ DATE_RE = %r<(\d{4})[./-]?(\d{2})[./-]?(\d{2})(?:[\sT]?(\d{2})[:.]?(\d{2})[:.]?(\d{2})?)?>
15
+
16
+ if ENV['RELEASE_DATE']
17
+ year, month, day, hour, minute, second = DATE_RE.match(ENV['RELEASE_DATE']).captures
18
+ year ||= 0
19
+ month ||= 0
20
+ day ||= 0
21
+ hour ||= 0
22
+ minute ||= 0
23
+ second ||= 0
24
+ ReleaseDate = Time.mktime(year, month, day, hour, minute, second)
25
+ else
26
+ ReleaseDate = nil
27
+ end
28
+
29
+ task :test do |t|
30
+ require 'test/unit/testsuite'
31
+ require 'test/unit/ui/console/testrunner'
32
+
33
+ runner = Test::Unit::UI::Console::TestRunner
34
+
35
+ $LOAD_PATH.unshift('tests')
36
+ $stderr.puts "Checking for test cases:" if t.verbose
37
+ Dir['tests/*test*.rb'].each do |testcase|
38
+ $stderr.puts "\t#{testcase}" if t.verbose
39
+ load testcase
40
+ end
41
+
42
+ suite = Test::Unit::TestSuite.new("Transaction::Simple")
43
+
44
+ ObjectSpace.each_object(Class) do |testcase|
45
+ suite << testcase.suite if testcase < Test::Unit::TestCase
46
+ end
47
+
48
+ runner.run(suite)
49
+ end
50
+
51
+ spec = eval(File.read("transaction-simple.gemspec"))
52
+ spec.version = Transaction::Simple::VERSION
53
+ desc "Build the RubyGem for Transaction::Simple"
54
+ task :gem => [ :test ]
55
+ Rake::GemPackageTask.new(spec) do |g|
56
+ g.need_tar = false
57
+ g.need_zip = false
58
+ g.package_dir = ".."
59
+ end
60
+
61
+ desc "Build a Transaction::Simple .tar.gz distribution."
62
+ task :tar => [ TARDIST ]
63
+ file TARDIST => [ :test ] do |t|
64
+ current = File.basename(Dir.pwd)
65
+ Dir.chdir("..") do
66
+ begin
67
+ files = Dir["#{current}/**/*"].select { |dd| dd !~ %r{(?:/CVS/?|~$)} }
68
+ files.map! do |dd|
69
+ ddnew = dd.gsub(/^#{current}/, DISTDIR)
70
+ mtime = ReleaseDate || File.stat(dd).mtime
71
+ if File.directory?(dd)
72
+ { :name => ddnew, :mode => 0755, :dir => true, :mtime => mtime }
73
+ else
74
+ if dd =~ %r{bin/}
75
+ mode = 0755
76
+ else
77
+ mode = 0644
78
+ end
79
+ data = File.read(dd)
80
+ { :name => ddnew, :mode => mode, :data => data, :size => data.size,
81
+ :mtime => mtime }
82
+ end
83
+ end
84
+
85
+ ff = File.open(t.name.gsub(%r{^\.\./}o, ''), "wb")
86
+ gz = Zlib::GzipWriter.new(ff)
87
+ tw = Archive::Tar::Minitar::Writer.new(gz)
88
+
89
+ files.each do |entry|
90
+ if entry[:dir]
91
+ tw.mkdir(entry[:name], entry)
92
+ else
93
+ tw.add_file_simple(entry[:name], entry) { |os| os.write(entry[:data]) }
94
+ end
95
+ end
96
+ ensure
97
+ tw.close if tw
98
+ gz.close if gz
99
+ end
100
+ end
101
+ end
102
+ task TARDIST => [ :test ]
103
+
104
+ def sign(file)
105
+ begin
106
+ sh %("C:\\Program Files\\Windows Privacy Tools\\GnuPG\\Gpg.exe" -ba #{file.gsub(%r</>) do '\\'; end})
107
+ rescue RuntimeError
108
+ raise unless File.exist?("#{file}.asc")
109
+ end
110
+ end
111
+
112
+ task :signtar => [ :tar ] do
113
+ sign TARDIST
114
+ end
115
+ task :signgem => [ :gem ] do
116
+ sign "../#{DISTDIR}.gem"
117
+ end
118
+
119
+ desc "Build everything."
120
+ task :default => [ :signtar, :signgem ]
data/Readme ADDED
@@ -0,0 +1,110 @@
1
+ Transaction::Simple for Ruby
2
+ Simple object transaction support for Ruby
3
+
4
+ Introduction
5
+ ------------
6
+ Transaction::Simple provides a generic way to add active transactional support
7
+ to objects. The transaction methods added by this module will work with most
8
+ objects, excluding those that cannot be Marshal-ed (bindings, procedure
9
+ objects, IO instances, or singleton objects).
10
+
11
+ The transactions supported by Transaction::Simple are not backend transaction;
12
+ that is, they have nothing to do with any sort of data store. They are "live"
13
+ transactions occurring in memory and in the object itself. This is to allow
14
+ "test" changes to be made to an object before making the changes permanent.
15
+
16
+ Transaction::Simple can handle an "infinite" number of transactional levels
17
+ (limited only by memory). If I open two transactions, commit the first, but
18
+ abort the second, the object will revert to the original version.
19
+
20
+ Transaction::Simple supports "named" transactions, so that multiple levels of
21
+ transactions can be committed, aborted, or rewound by referring to the
22
+ appropriate name of the transaction. Names may be any object except nil.
23
+
24
+ Copyright: Copyright � 2003 by Austin Ziegler
25
+ Version: 1.11
26
+ Licence: MIT-Style
27
+
28
+ Thanks to David Black and Mauricio Fern�ndez for their help with this library.
29
+
30
+ Usage
31
+ -----
32
+ include 'transaction/simple'
33
+
34
+ v = "Hello, you." # => "Hello, you."
35
+ v.extend(Transaction::Simple) # => "Hello, you."
36
+
37
+ v.start_transaction # => ... (a Marshal string)
38
+ v.transaction_open? # => true
39
+ v.gsub!(/you/, "world") # => "Hello, world."
40
+
41
+ v.rewind_transaction # => "Hello, you."
42
+ v.transaction_open? # => true
43
+
44
+ v.gsub!(/you/, "HAL") # => "Hello, HAL."
45
+ v.abort_transaction # => "Hello, you."
46
+ v.transaction_open? # => false
47
+
48
+ v.start_transaction # => ... (a Marshal string)
49
+ v.start_transaction # => ... (a Marshal string)
50
+
51
+ v.transaction_open? # => true
52
+ v.gsub!(/you/, "HAL") # => "Hello, HAL."
53
+
54
+ v.commit_transaction # => "Hello, HAL."
55
+ v.transaction_open? # => true
56
+ v.abort_transaction # => "Hello, you."
57
+ v.transaction_open? # => false
58
+
59
+ Named Transaction Usage
60
+ -----------------------
61
+ v = "Hello, you." # => "Hello, you."
62
+ v.extend(Transaction::Simple) # => "Hello, you."
63
+
64
+ v.start_transaction(:first) # => ... (a Marshal string)
65
+ v.transaction_open? # => true
66
+ v.transaction_open?(:first) # => true
67
+ v.transaction_open?(:second) # => false
68
+ v.gsub!(/you/, "world") # => "Hello, world."
69
+
70
+ v.start_transaction(:second) # => ... (a Marshal string)
71
+ v.gsub!(/world/, "HAL") # => "Hello, HAL."
72
+ v.rewind_transaction(:first) # => "Hello, you."
73
+ v.transaction_open? # => true
74
+ v.transaction_open?(:first) # => true
75
+ v.transaction_open?(:second) # => false
76
+
77
+ v.gsub!(/you/, "world") # => "Hello, world."
78
+ v.start_transaction(:second) # => ... (a Marshal string)
79
+ v.gsub!(/world/, "HAL") # => "Hello, HAL."
80
+ v.transaction_name # => :second
81
+ v.abort_transaction(:first) # => "Hello, you."
82
+ v.transaction_open? # => false
83
+
84
+ v.start_transaction(:first) # => ... (a Marshal string)
85
+ v.gsub!(/you/, "world") # => "Hello, world."
86
+ v.start_transaction(:second) # => ... (a Marshal string)
87
+ v.gsub!(/world/, "HAL") # => "Hello, HAL."
88
+
89
+ v.commit_transaction(:first) # => "Hello, HAL."
90
+ v.transaction_open? # => false
91
+
92
+ Contraindications
93
+ -----------------
94
+ While Transaction::Simple is very useful, it has some severe limitations that
95
+ must be understood. Transaction::Simple:
96
+
97
+ * uses Marshal. Thus, any object which cannot be Marshal-ed cannot use
98
+ Transaction::Simple.
99
+ * does not manage resources. Resources external to the object and its instance
100
+ variables are not managed at all. However, all instance variables and
101
+ objects "belonging" to those instance variables are managed. If there are
102
+ object reference counts to be handled, Transaction::Simple will probably
103
+ cause problems.
104
+ * is not thread-safe. In the ACID ("atomic, consistent, isolated, durable")
105
+ test, Transaction::Simple provides C and D, but it is up to the user of
106
+ Transaction::Simple to provide isolation. Transactions should be considered
107
+ "critical sections" in multi-threaded applications. Thread safety can be
108
+ ensured with Transaction::Simple::ThreadSafe.
109
+ * does not maintain Object#__id__ values on rewind or abort. This may change
110
+ for future versions that will be Ruby 1.8 or better only.
@@ -0,0 +1,591 @@
1
+ # :title: Transaction::Simple
2
+ # :main: Transaction::Simple
3
+ #
4
+ # == Licence
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a
7
+ # copy of this software and associated documentation files (the "Software"),
8
+ # to deal in the Software without restriction, including without limitation
9
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
10
+ # and/or sell copies of the Software, and to permit persons to whom the
11
+ # Software is furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22
+ # DEALINGS IN THE SOFTWARE.
23
+ #--
24
+ # Transaction::Simple
25
+ # Simple object transaction support for Ruby
26
+ # Version 1.2.0
27
+ #
28
+ # Copyright (c) 2003 - 2004 Austin Ziegler
29
+ #
30
+ # $Id: simple.rb,v 1.2 2004/09/14 18:46:15 austin Exp $
31
+ #++
32
+ # Required for Transaction::Simple::ThreadSafe
33
+ require 'thread'
34
+
35
+ # The "Transaction" namespace can be used for additional transactional
36
+ # support objects and modules.
37
+ module Transaction
38
+ # A standard exception for transactional errors.
39
+ class TransactionError < StandardError; end
40
+ class TransactionAborted < Exception; end
41
+ class TransactionCommitted < Exception; end
42
+ # A standard exception for transactional errors involving the acquisition
43
+ # of locks for Transaction::Simple::ThreadSafe.
44
+ class TransactionThreadError < StandardError; end
45
+
46
+ # = Transaction::Simple for Ruby
47
+ # Simple object transaction support for Ruby
48
+ #
49
+ # == Introduction
50
+ #
51
+ # Transaction::Simple provides a generic way to add active transactional
52
+ # support to objects. The transaction methods added by this module will
53
+ # work with most objects, excluding those that cannot be
54
+ # <i>Marshal</i>ed (bindings, procedure objects, IO instances, or
55
+ # singleton objects).
56
+ #
57
+ # The transactions supported by Transaction::Simple are not backed
58
+ # transactions; that is, they have nothing to do with any sort of data
59
+ # store. They are "live" transactions occurring in memory and in the
60
+ # object itself. This is to allow "test" changes to be made to an object
61
+ # before making the changes permanent.
62
+ #
63
+ # Transaction::Simple can handle an "infinite" number of transactional
64
+ # levels (limited only by memory). If I open two transactions, commit
65
+ # the first, but abort the second, the object will revert to the
66
+ # original version.
67
+ #
68
+ # Transaction::Simple supports "named" transactions, so that multiple
69
+ # levels of transactions can be committed, aborted, or rewound by
70
+ # referring to the appropriate name of the transaction. Names may be any
71
+ # object *except* +nil+.
72
+ #
73
+ # Copyright:: Copyright � 2003 - 2004 by Austin Ziegler
74
+ # Version:: 1.2
75
+ # Licence:: MIT-Style
76
+ #
77
+ # Thanks to David Black for help with the initial concept that led to
78
+ # this library.
79
+ #
80
+ # == Usage
81
+ # include 'transaction/simple'
82
+ #
83
+ # v = "Hello, you." # => "Hello, you."
84
+ # v.extend(Transaction::Simple) # => "Hello, you."
85
+ #
86
+ # v.start_transaction # => ... (a Marshal string)
87
+ # v.transaction_open? # => true
88
+ # v.gsub!(/you/, "world") # => "Hello, world."
89
+ #
90
+ # v.rewind_transaction # => "Hello, you."
91
+ # v.transaction_open? # => true
92
+ #
93
+ # v.gsub!(/you/, "HAL") # => "Hello, HAL."
94
+ # v.abort_transaction # => "Hello, you."
95
+ # v.transaction_open? # => false
96
+ #
97
+ # v.start_transaction # => ... (a Marshal string)
98
+ # v.start_transaction # => ... (a Marshal string)
99
+ #
100
+ # v.transaction_open? # => true
101
+ # v.gsub!(/you/, "HAL") # => "Hello, HAL."
102
+ #
103
+ # v.commit_transaction # => "Hello, HAL."
104
+ # v.transaction_open? # => true
105
+ # v.abort_transaction # => "Hello, you."
106
+ # v.transaction_open? # => false
107
+ #
108
+ # == Named Transaction Usage
109
+ # v = "Hello, you." # => "Hello, you."
110
+ # v.extend(Transaction::Simple) # => "Hello, you."
111
+ #
112
+ # v.start_transaction(:first) # => ... (a Marshal string)
113
+ # v.transaction_open? # => true
114
+ # v.transaction_open?(:first) # => true
115
+ # v.transaction_open?(:second) # => false
116
+ # v.gsub!(/you/, "world") # => "Hello, world."
117
+ #
118
+ # v.start_transaction(:second) # => ... (a Marshal string)
119
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
120
+ # v.rewind_transaction(:first) # => "Hello, you."
121
+ # v.transaction_open? # => true
122
+ # v.transaction_open?(:first) # => true
123
+ # v.transaction_open?(:second) # => false
124
+ #
125
+ # v.gsub!(/you/, "world") # => "Hello, world."
126
+ # v.start_transaction(:second) # => ... (a Marshal string)
127
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
128
+ # v.transaction_name # => :second
129
+ # v.abort_transaction(:first) # => "Hello, you."
130
+ # v.transaction_open? # => false
131
+ #
132
+ # v.start_transaction(:first) # => ... (a Marshal string)
133
+ # v.gsub!(/you/, "world") # => "Hello, world."
134
+ # v.start_transaction(:second) # => ... (a Marshal string)
135
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
136
+ #
137
+ # v.commit_transaction(:first) # => "Hello, HAL."
138
+ # v.transaction_open? # => false
139
+ #
140
+ # == Block Usage
141
+ # include 'transaction/simple'
142
+ #
143
+ # v = "Hello, you." # => "Hello, you."
144
+ # Transaction::Simple.start(v) do |tv|
145
+ # # v has been extended with Transaction::Simple and an unnamed
146
+ # # transaction has been started.
147
+ # tv.transaction_open? # => true
148
+ # tv.gsub!(/you/, "world") # => "Hello, world."
149
+ #
150
+ # tv.rewind_transaction # => "Hello, you."
151
+ # tv.transaction_open? # => true
152
+ #
153
+ # tv.gsub!(/you/, "HAL") # => "Hello, HAL."
154
+ # # The following breaks out of the transaction block after
155
+ # # aborting the transaction.
156
+ # tv.abort_transaction # => "Hello, you."
157
+ # end
158
+ # # v still has Transaction::Simple applied from here on out.
159
+ # v.transaction_open? # => false
160
+ #
161
+ # Transaction::Simple.start(v) do |tv|
162
+ # tv.start_transaction # => ... (a Marshal string)
163
+ #
164
+ # tv.transaction_open? # => true
165
+ # tv.gsub!(/you/, "HAL") # => "Hello, HAL."
166
+ #
167
+ # # If #commit_transaction were called without having started a
168
+ # # second transaction, then it would break out of the transaction
169
+ # # block after committing the transaction.
170
+ # tv.commit_transaction # => "Hello, HAL."
171
+ # tv.transaction_open? # => true
172
+ # tv.abort_transaction # => "Hello, you."
173
+ # end
174
+ # v.transaction_open? # => false
175
+ #
176
+ # == Named Transaction Usage
177
+ # v = "Hello, you." # => "Hello, you."
178
+ # v.extend(Transaction::Simple) # => "Hello, you."
179
+ #
180
+ # v.start_transaction(:first) # => ... (a Marshal string)
181
+ # v.transaction_open? # => true
182
+ # v.transaction_open?(:first) # => true
183
+ # v.transaction_open?(:second) # => false
184
+ # v.gsub!(/you/, "world") # => "Hello, world."
185
+ #
186
+ # v.start_transaction(:second) # => ... (a Marshal string)
187
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
188
+ # v.rewind_transaction(:first) # => "Hello, you."
189
+ # v.transaction_open? # => true
190
+ # v.transaction_open?(:first) # => true
191
+ # v.transaction_open?(:second) # => false
192
+ #
193
+ # v.gsub!(/you/, "world") # => "Hello, world."
194
+ # v.start_transaction(:second) # => ... (a Marshal string)
195
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
196
+ # v.transaction_name # => :second
197
+ # v.abort_transaction(:first) # => "Hello, you."
198
+ # v.transaction_open? # => false
199
+ #
200
+ # v.start_transaction(:first) # => ... (a Marshal string)
201
+ # v.gsub!(/you/, "world") # => "Hello, world."
202
+ # v.start_transaction(:second) # => ... (a Marshal string)
203
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
204
+ #
205
+ # v.commit_transaction(:first) # => "Hello, HAL."
206
+ # v.transaction_open? # => false
207
+ #
208
+ # == Contraindications
209
+ #
210
+ # While Transaction::Simple is very useful, it has some severe
211
+ # limitations that must be understood. Transaction::Simple:
212
+ #
213
+ # * uses Marshal. Thus, any object which cannot be <i>Marshal</i>ed
214
+ # cannot use Transaction::Simple.
215
+ # * does not manage resources. Resources external to the object and its
216
+ # instance variables are not managed at all. However, all instance
217
+ # variables and objects "belonging" to those instance variables are
218
+ # managed. If there are object reference counts to be handled,
219
+ # Transaction::Simple will probably cause problems.
220
+ # * is not inherently thread-safe. In the ACID ("atomic, consistent,
221
+ # isolated, durable") test, Transaction::Simple provides CD, but it is
222
+ # up to the user of Transaction::Simple to provide isolation and
223
+ # atomicity. Transactions should be considered "critical sections" in
224
+ # multi-threaded applications. If thread safety and atomicity is
225
+ # absolutely required, use Transaction::Simple::ThreadSafe, which uses
226
+ # a Mutex object to synchronize the accesses on the object during the
227
+ # transactional operations.
228
+ # * does not necessarily maintain Object#__id__ values on rewind or
229
+ # abort. This may change for future versions that will be Ruby 1.8 or
230
+ # better *only*. Certain objects that support #replace will maintain
231
+ # Object#__id__.
232
+ # * Can be a memory hog if you use many levels of transactions on many
233
+ # objects.
234
+ #
235
+ module Simple
236
+ VERSION = '1.2.0'
237
+
238
+ # Sets the Transaction::Simple debug object. It must respond to #<<.
239
+ # Sets the transaction debug object. Debugging will be performed
240
+ # automatically if there's a debug object. The generic transaction
241
+ # error class.
242
+ def self.debug_io=(io)
243
+ raise TransactionError, "Transaction Error: the transaction debug object must respond to #<<" unless io.respond_to?(:<<)
244
+ @tdi = io
245
+ end
246
+
247
+ # Returns the Transaction::Simple debug object. It must respond to
248
+ # #<<.
249
+ def self.debug_io
250
+ @tdi
251
+ end
252
+
253
+ # If +name+ is +nil+ (default), then returns +true+ if there is
254
+ # currently a transaction open.
255
+ #
256
+ # If +name+ is specified, then returns +true+ if there is currently a
257
+ # transaction that responds to +name+ open.
258
+ def transaction_open?(name = nil)
259
+ if name.nil?
260
+ Transaction::Simple.debug_io << "Transaction [#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" unless Transaction::Simple.debug_io.nil?
261
+ return (not @__transaction_checkpoint__.nil?)
262
+ else
263
+ Transaction::Simple.debug_io << "Transaction(#{name.inspect}) [#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" unless Transaction::Simple.debug_io.nil?
264
+ return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
265
+ end
266
+ end
267
+
268
+ # Returns the current name of the transaction. Transactions not
269
+ # explicitly named are named +nil+.
270
+ def transaction_name
271
+ raise TransactionError, "Transaction Error: No transaction open." if @__transaction_checkpoint__.nil?
272
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Transaction Name: #{@__transaction_names__[-1].inspect}\n" unless Transaction::Simple.debug_io.nil?
273
+ @__transaction_names__[-1]
274
+ end
275
+
276
+ # Starts a transaction. Stores the current object state. If a
277
+ # transaction name is specified, the transaction will be named.
278
+ # Transaction names must be unique. Transaction names of +nil+ will be
279
+ # treated as unnamed transactions.
280
+ def start_transaction(name = nil)
281
+ @__transaction_level__ ||= 0
282
+ @__transaction_names__ ||= []
283
+
284
+ if name.nil?
285
+ @__transaction_names__ << nil
286
+ ss = ""
287
+ else
288
+ raise TransactionError, "Transaction Error: Named transactions must be unique." if @__transaction_names__.include?(name)
289
+ @__transaction_names__ << name
290
+ ss = "(#{name.inspect})"
291
+ end
292
+
293
+ @__transaction_level__ += 1
294
+
295
+ Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} Start Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
296
+
297
+ @__transaction_checkpoint__ = Marshal.dump(self)
298
+ end
299
+
300
+ # Rewinds the transaction. If +name+ is specified, then the
301
+ # intervening transactions will be aborted and the named transaction
302
+ # will be rewound. Otherwise, only the current transaction is rewound.
303
+ def rewind_transaction(name = nil)
304
+ raise TransactionError, "Transaction Error: Cannot rewind. There is no current transaction." if @__transaction_checkpoint__.nil?
305
+ if name.nil?
306
+ __rewind_this_transaction
307
+ ss = ""
308
+ else
309
+ raise TransactionError, "Transaction Error: Cannot rewind to transaction #{name.inspect} because it does not exist." unless @__transaction_names__.include?(name)
310
+ ss = "(#{name})"
311
+
312
+ while @__transaction_names__[-1] != name
313
+ @__transaction_checkpoint__ = __rewind_this_transaction
314
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Rewind Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
315
+ @__transaction_level__ -= 1
316
+ @__transaction_names__.pop
317
+ end
318
+ __rewind_this_transaction
319
+ end
320
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Rewind Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
321
+ self
322
+ end
323
+
324
+ # Aborts the transaction. Resets the object state to what it was
325
+ # before the transaction was started and closes the transaction. If
326
+ # +name+ is specified, then the intervening transactions and the named
327
+ # transaction will be aborted. Otherwise, only the current transaction
328
+ # is aborted.
329
+ #
330
+ # If the current or named transaction has been started by a block
331
+ # (Transaction::Simple.start), then the execution of the block will be
332
+ # halted with +break+ +self+.
333
+ def abort_transaction(name = nil)
334
+ raise TransactionError, "Transaction Error: Cannot abort. There is no current transaction." if @__transaction_checkpoint__.nil?
335
+
336
+ # Check to see if we are trying to abort a transaction that is
337
+ # outside of the current transaction block. Otherwise, raise
338
+ # TransactionAborted if they are the same.
339
+ if @__transaction_block__ and name
340
+ nix = @__transaction_names__.index(name) + 1
341
+ raise TransactionError, "Transaction Error: Cannot abort a transaction outside of this execution block." if nix < @__transaction_block__
342
+
343
+ raise TransactionAborted if @__transaction_block__ == nix
344
+ end
345
+
346
+ raise TransactionAborted if @__transaction_block__ == @__transaction_level__
347
+
348
+ if name.nil?
349
+ __abort_transaction(name)
350
+ else
351
+ raise TransactionError, "Transaction Error: Cannot abort nonexistant transaction #{name.inspect}." unless @__transaction_names__.include?(name)
352
+
353
+ __abort_transaction(name) while @__transaction_names__.include?(name)
354
+ end
355
+ self
356
+ end
357
+
358
+ # If +name+ is +nil+ (default), the current transaction level is
359
+ # closed out and the changes are committed.
360
+ #
361
+ # If +name+ is specified and +name+ is in the list of named
362
+ # transactions, then all transactions are closed and committed until
363
+ # the named transaction is reached.
364
+ def commit_transaction(name = nil)
365
+ raise TransactionError, "Transaction Error: Cannot commit. There is no current transaction." if @__transaction_checkpoint__.nil?
366
+
367
+ # Check to see if we are trying to commit a transaction that is
368
+ # outside of the current transaction block. Otherwise, raise
369
+ # TransactionCommitted if they are the same.
370
+ if @__transaction_block__ and name
371
+ nix = @__transaction_names__.index(name) + 1
372
+ raise TransactionError, "Transaction Error: Cannot commit a transaction outside of this execution block." if nix < @__transaction_block__
373
+
374
+ raise TransactionCommitted if @__transaction_block__ == nix
375
+ end
376
+
377
+ raise TransactionCommitted if @__transaction_block__ == @__transaction_level__
378
+
379
+ if name.nil?
380
+ ss = ""
381
+ __commit_transaction
382
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
383
+ else
384
+ raise TransactionError, "Transaction Error: Cannot commit nonexistant transaction #{name.inspect}." unless @__transaction_names__.include?(name)
385
+ ss = "(#{name})"
386
+
387
+ while @__transaction_names__[-1] != name
388
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
389
+ __commit_transaction
390
+ end
391
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
392
+ __commit_transaction
393
+ end
394
+
395
+ self
396
+ end
397
+
398
+ # Alternative method for calling the transaction methods. An optional
399
+ # name can be specified for named transaction support.
400
+ #
401
+ # #transaction(:start):: #start_transaction
402
+ # #transaction(:rewind):: #rewind_transaction
403
+ # #transaction(:abort):: #abort_transaction
404
+ # #transaction(:commit):: #commit_transaction
405
+ # #transaction(:name):: #transaction_name
406
+ # #transaction:: #transaction_open?
407
+ def transaction(action = nil, name = nil)
408
+ case action
409
+ when :start
410
+ start_transaction(name)
411
+ when :rewind
412
+ rewind_transaction(name)
413
+ when :abort
414
+ abort_transaction(name)
415
+ when :commit
416
+ commit_transaction(name)
417
+ when :name
418
+ transaction_name
419
+ when nil
420
+ transaction_open?(name)
421
+ end
422
+ end
423
+
424
+ class << self
425
+ def __common_start(name, vars, &block)
426
+ raise TransactionError, "Transaction Error: Cannot start a transaction with no objects." if vars.empty?
427
+
428
+ if block
429
+ begin
430
+ vlevel = {}
431
+
432
+ vars.each do |vv|
433
+ vv.extend(Transaction::Simple)
434
+ vv.start_transaction(name)
435
+ vlevel[vv.__id__] = vv.instance_variable_get(:@__transaction_level__)
436
+ vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__])
437
+ end
438
+
439
+ yield *vars
440
+ rescue TransactionAborted
441
+ vars.each do |vv|
442
+ if name.nil? and vv.transaction_open?
443
+ loop do
444
+ tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
445
+ vv.instance_variable_set(:@__transaction_block__, -1)
446
+ break if tlevel < vlevel[vv.__id__]
447
+ vv.abort_transaction if vv.transaction_open?
448
+ end
449
+ elsif vv.transaction_open?(name)
450
+ vv.instance_variable_set(:@__transaction_block__, -1)
451
+ vv.abort_transaction(name)
452
+ end
453
+ end
454
+ rescue TransactionCommitted
455
+ nil
456
+ ensure
457
+ vars.each do |vv|
458
+ if name.nil? and vv.transaction_open?
459
+ loop do
460
+ tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
461
+ break if tlevel < vlevel[vv.__id__]
462
+ vv.instance_variable_set(:@__transaction_block__, -1)
463
+ vv.commit_transaction if vv.transaction_open?
464
+ end
465
+ elsif vv.transaction_open?(name)
466
+ vv.instance_variable_set(:@__transaction_block__, -1)
467
+ vv.commit_transaction(name)
468
+ end
469
+ end
470
+ end
471
+ else
472
+ vars.each do |vv|
473
+ vv.extend(Transaction::Simple)
474
+ vv.start_transaction(name)
475
+ end
476
+ end
477
+ end
478
+ private :__common_start
479
+
480
+ def start_named(name, *vars, &block)
481
+ __common_start(name, vars, &block)
482
+ end
483
+
484
+ def start(*vars, &block)
485
+ __common_start(nil, vars, &block)
486
+ end
487
+ end
488
+
489
+ def __abort_transaction(name = nil) #:nodoc:
490
+ @__transaction_checkpoint__ = __rewind_this_transaction
491
+
492
+ if name.nil?
493
+ ss = ""
494
+ else
495
+ ss = "(#{name.inspect})"
496
+ end
497
+
498
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Abort Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
499
+ @__transaction_level__ -= 1
500
+ @__transaction_names__.pop
501
+ if @__transaction_level__ < 1
502
+ @__transaction_level__ = 0
503
+ @__transaction_names__ = []
504
+ end
505
+ end
506
+
507
+ TRANSACTION_CHECKPOINT = "@__transaction_checkpoint__" #:nodoc:
508
+ SKIP_TRANSACTION_VARS = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc:
509
+
510
+ def __rewind_this_transaction #:nodoc:
511
+ r = Marshal.restore(@__transaction_checkpoint__)
512
+
513
+ begin
514
+ self.replace(r) if respond_to?(:replace)
515
+ rescue
516
+ nil
517
+ end
518
+
519
+ r.instance_variables.each do |i|
520
+ next if SKIP_TRANSACTION_VARS.include?(i)
521
+ if respond_to?(:instance_variable_get)
522
+ instance_variable_set(i, r.instance_variable_get(i))
523
+ else
524
+ instance_eval(%q|#{i} = r.instance_eval("#{i}")|)
525
+ end
526
+ end
527
+
528
+ if respond_to?(:instance_variable_get)
529
+ return r.instance_variable_get(TRANSACTION_CHECKPOINT)
530
+ else
531
+ return r.instance_eval(TRANSACTION_CHECKPOINT)
532
+ end
533
+ end
534
+
535
+ def __commit_transaction #:nodoc:
536
+ if respond_to?(:instance_variable_get)
537
+ @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_variable_get(TRANSACTION_CHECKPOINT)
538
+ else
539
+ @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_eval(TRANSACTION_CHECKPOINT)
540
+ end
541
+
542
+ @__transaction_level__ -= 1
543
+ @__transaction_names__.pop
544
+ if @__transaction_level__ < 1
545
+ @__transaction_level__ = 0
546
+ @__transaction_names__ = []
547
+ end
548
+ end
549
+
550
+ private :__abort_transaction, :__rewind_this_transaction, :__commit_transaction
551
+
552
+ # = Transaction::Simple::ThreadSafe
553
+ # Thread-safe simple object transaction support for Ruby.
554
+ # Transaction::Simple::ThreadSafe is used in the same way as
555
+ # Transaction::Simple. Transaction::Simple::ThreadSafe uses a Mutex
556
+ # object to ensure atomicity at the cost of performance in threaded
557
+ # applications.
558
+ #
559
+ # Transaction::Simple::ThreadSafe will not wait to obtain a lock; if the
560
+ # lock cannot be obtained immediately, a
561
+ # Transaction::TransactionThreadError will be raised.
562
+ #
563
+ # Thanks to Mauricio Fern�ndez for help with getting this part working.
564
+ module ThreadSafe
565
+ VERSION = '1.2.0'
566
+
567
+ include Transaction::Simple
568
+
569
+ SKIP_TRANSACTION_VARS = Transaction::Simple::SKIP_TRANSACTION_VARS.dup #:nodoc:
570
+ SKIP_TRANSACTION_VARS << "@__transaction_mutex__"
571
+
572
+ Transaction::Simple.instance_methods(false) do |meth|
573
+ next if meth == "transaction"
574
+ arg = "(name = nil)" unless meth == "transaction_name"
575
+ module_eval <<-EOS
576
+ def #{meth}#{arg}
577
+ if (@__transaction_mutex__ ||= Mutex.new).try_lock
578
+ result = super
579
+ @__transaction_mutex__.unlock
580
+ return result
581
+ else
582
+ raise TransactionThreadError, "Transaction Error: Cannot obtain lock for ##{meth}"
583
+ end
584
+ ensure
585
+ @__transaction_mutex__.unlock
586
+ end
587
+ EOS
588
+ end
589
+ end
590
+ end
591
+ end