transaction-simple 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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