transaction-simple 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,693 +1,486 @@
1
1
  # :title: Transaction::Simple -- Active Object Transaction Support for Ruby
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.
2
+ # :main: Readme.txt
3
+
23
4
  #--
24
5
  # Transaction::Simple
25
- # Simple object transaction support for Ruby
26
- # Version 1.3.0
6
+ # Simple object transaction support for Ruby
7
+ # http://rubyforge.org/projects/trans-simple/
8
+ # Version 1.4.0
9
+ #
10
+ # Licensed under a MIT-style licence. See Licence.txt in the main
11
+ # distribution for full licensing information.
27
12
  #
28
- # Copyright (c) 2003 - 2005 Austin Ziegler
13
+ # Copyright (c) 2003 - 2007 Austin Ziegler
29
14
  #
30
- # $Id: simple.rb,v 1.5 2005/05/05 16:16:49 austin Exp $
15
+ # $Id: simple.rb 50 2007-02-03 20:26:19Z austin $
31
16
  #++
32
- # The "Transaction" namespace can be used for additional transaction
33
- # support objects and modules.
17
+
18
+ # The "Transaction" namespace can be used for additional transaction support
19
+ # objects and modules.
34
20
  module Transaction
35
- # A standard exception for transaction errors.
21
+ # A standard exception for transaction errors.
36
22
  class TransactionError < StandardError; end
37
- # The TransactionAborted exception is used to indicate when a
38
- # transaction has been aborted in the block form.
23
+ # The TransactionAborted exception is used to indicate when a transaction
24
+ # has been aborted in the block form.
39
25
  class TransactionAborted < Exception; end
40
- # The TransactionCommitted exception is used to indicate when a
41
- # transaction has been committed in the block form.
26
+ # The TransactionCommitted exception is used to indicate when a
27
+ # transaction has been committed in the block form.
42
28
  class TransactionCommitted < Exception; end
43
29
 
44
30
  te = "Transaction Error: %s"
45
31
 
46
- Messages = {
47
- :bad_debug_object =>
48
- te % "the transaction debug object must respond to #<<.",
49
- :unique_names =>
50
- te % "named transactions must be unique.",
51
- :no_transaction_open =>
52
- te % "no transaction open.",
53
- :cannot_rewind_no_transaction =>
54
- te % "cannot rewind; there is no current transaction.",
55
- :cannot_rewind_named_transaction =>
56
- te % "cannot rewind to transaction %s because it does not exist.",
57
- :cannot_rewind_transaction_before_block =>
58
- te % "cannot rewind a transaction started before the execution block.",
59
- :cannot_abort_no_transaction =>
60
- te % "cannot abort; there is no current transaction.",
61
- :cannot_abort_transaction_before_block =>
62
- te % "cannot abort a transaction started before the execution block.",
63
- :cannot_abort_named_transaction =>
64
- te % "cannot abort nonexistant transaction %s.",
65
- :cannot_commit_no_transaction =>
66
- te % "cannot commit; there is no current transaction.",
67
- :cannot_commit_transaction_before_block =>
68
- te % "cannot commit a transaction started before the execution block.",
69
- :cannot_commit_named_transaction =>
70
- te % "cannot commit nonexistant transaction %s.",
71
- :cannot_start_empty_block_transaction =>
72
- te % "cannot start a block transaction with no objects.",
73
- :cannot_obtain_transaction_lock =>
74
- te % "cannot obtain transaction lock for #%s.",
32
+ Messages = { #:nodoc:
33
+ :bad_debug_object => te % "the transaction debug object must respond to #<<.",
34
+ :unique_names => te % "named transactions must be unique.",
35
+ :no_transaction_open => te % "no transaction open.",
36
+ :cannot_rewind_no_transaction => te % "cannot rewind; there is no current transaction.",
37
+ :cannot_rewind_named_transaction => te % "cannot rewind to transaction %s because it does not exist.",
38
+ :cannot_rewind_transaction_before_block => te % "cannot rewind a transaction started before the execution block.",
39
+ :cannot_abort_no_transaction => te % "cannot abort; there is no current transaction.",
40
+ :cannot_abort_transaction_before_block => te % "cannot abort a transaction started before the execution block.",
41
+ :cannot_abort_named_transaction => te % "cannot abort nonexistant transaction %s.",
42
+ :cannot_commit_no_transaction => te % "cannot commit; there is no current transaction.",
43
+ :cannot_commit_transaction_before_block => te % "cannot commit a transaction started before the execution block.",
44
+ :cannot_commit_named_transaction => te % "cannot commit nonexistant transaction %s.",
45
+ :cannot_start_empty_block_transaction => te % "cannot start a block transaction with no objects.",
46
+ :cannot_obtain_transaction_lock => te % "cannot obtain transaction lock for #%s.",
75
47
  }
48
+ end
49
+
50
+ # = Transaction::Simple for Ruby
51
+ # Simple object transaction support for Ruby
52
+ module Transaction::Simple
53
+ TRANSACTION_SIMPLE_VERSION = '1.4.0'
76
54
 
77
- # = Transaction::Simple for Ruby
78
- # Simple object transaction support for Ruby
79
- #
80
- # == Introduction
81
- # Transaction::Simple provides a generic way to add active transaction
82
- # support to objects. The transaction methods added by this module will
83
- # work with most objects, excluding those that cannot be
84
- # <i>Marshal</i>ed (bindings, procedure objects, IO instances, or
85
- # singleton objects).
86
- #
87
- # The transactions supported by Transaction::Simple are not backed
88
- # transactions; they are not associated with any sort of data store.
89
- # They are "live" transactions occurring in memory and in the object
90
- # itself. This is to allow "test" changes to be made to an object
91
- # before making the changes permanent.
92
- #
93
- # Transaction::Simple can handle an "infinite" number of transaction
94
- # levels (limited only by memory). If I open two transactions, commit
95
- # the second, but abort the first, the object will revert to the
96
- # original version.
97
- #
98
- # Transaction::Simple supports "named" transactions, so that multiple
99
- # levels of transactions can be committed, aborted, or rewound by
100
- # referring to the appropriate name of the transaction. Names may be any
101
- # object *except* +nil+. As with Hash keys, String names will be
102
- # duplicated and frozen before using.
103
- #
104
- # Copyright:: Copyright � 2003 - 2005 by Austin Ziegler
105
- # Version:: 1.3.0
106
- # Licence:: MIT-Style
107
- #
108
- # Thanks to David Black for help with the initial concept that led to
109
- # this library.
110
- #
111
- # == Usage
112
- # include 'transaction/simple'
113
- #
114
- # v = "Hello, you." # -> "Hello, you."
115
- # v.extend(Transaction::Simple) # -> "Hello, you."
116
- #
117
- # v.start_transaction # -> ... (a Marshal string)
118
- # v.transaction_open? # -> true
119
- # v.gsub!(/you/, "world") # -> "Hello, world."
120
- #
121
- # v.rewind_transaction # -> "Hello, you."
122
- # v.transaction_open? # -> true
123
- #
124
- # v.gsub!(/you/, "HAL") # -> "Hello, HAL."
125
- # v.abort_transaction # -> "Hello, you."
126
- # v.transaction_open? # -> false
127
- #
128
- # v.start_transaction # -> ... (a Marshal string)
129
- # v.start_transaction # -> ... (a Marshal string)
130
- #
131
- # v.transaction_open? # -> true
132
- # v.gsub!(/you/, "HAL") # -> "Hello, HAL."
133
- #
134
- # v.commit_transaction # -> "Hello, HAL."
135
- # v.transaction_open? # -> true
136
- # v.abort_transaction # -> "Hello, you."
137
- # v.transaction_open? # -> false
138
- #
139
- # == Named Transaction Usage
140
- # v = "Hello, you." # -> "Hello, you."
141
- # v.extend(Transaction::Simple) # -> "Hello, you."
142
- #
143
- # v.start_transaction(:first) # -> ... (a Marshal string)
144
- # v.transaction_open? # -> true
145
- # v.transaction_open?(:first) # -> true
146
- # v.transaction_open?(:second) # -> false
147
- # v.gsub!(/you/, "world") # -> "Hello, world."
148
- #
149
- # v.start_transaction(:second) # -> ... (a Marshal string)
150
- # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
151
- # v.rewind_transaction(:first) # -> "Hello, you."
152
- # v.transaction_open? # -> true
153
- # v.transaction_open?(:first) # -> true
154
- # v.transaction_open?(:second) # -> false
155
- #
156
- # v.gsub!(/you/, "world") # -> "Hello, world."
157
- # v.start_transaction(:second) # -> ... (a Marshal string)
158
- # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
159
- # v.transaction_name # -> :second
160
- # v.abort_transaction(:first) # -> "Hello, you."
161
- # v.transaction_open? # -> false
162
- #
163
- # v.start_transaction(:first) # -> ... (a Marshal string)
164
- # v.gsub!(/you/, "world") # -> "Hello, world."
165
- # v.start_transaction(:second) # -> ... (a Marshal string)
166
- # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
167
- #
168
- # v.commit_transaction(:first) # -> "Hello, HAL."
169
- # v.transaction_open? # -> false
170
- #
171
- # == Block Usage
172
- # v = "Hello, you." # -> "Hello, you."
173
- # Transaction::Simple.start(v) do |tv|
174
- # # v has been extended with Transaction::Simple and an unnamed
175
- # # transaction has been started.
176
- # tv.transaction_open? # -> true
177
- # tv.gsub!(/you/, "world") # -> "Hello, world."
178
- #
179
- # tv.rewind_transaction # -> "Hello, you."
180
- # tv.transaction_open? # -> true
181
- #
182
- # tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
183
- # # The following breaks out of the transaction block after
184
- # # aborting the transaction.
185
- # tv.abort_transaction # -> "Hello, you."
186
- # end
187
- # # v still has Transaction::Simple applied from here on out.
188
- # v.transaction_open? # -> false
189
- #
190
- # Transaction::Simple.start(v) do |tv|
191
- # tv.start_transaction # -> ... (a Marshal string)
192
- #
193
- # tv.transaction_open? # -> true
194
- # tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
195
- #
196
- # # If #commit_transaction were called without having started a
197
- # # second transaction, then it would break out of the transaction
198
- # # block after committing the transaction.
199
- # tv.commit_transaction # -> "Hello, HAL."
200
- # tv.transaction_open? # -> true
201
- # tv.abort_transaction # -> "Hello, you."
202
- # end
203
- # v.transaction_open? # -> false
204
- #
205
- # == Named Transaction Usage
206
- # v = "Hello, you." # -> "Hello, you."
207
- # v.extend(Transaction::Simple) # -> "Hello, you."
208
- #
209
- # v.start_transaction(:first) # -> ... (a Marshal string)
210
- # v.transaction_open? # -> true
211
- # v.transaction_open?(:first) # -> true
212
- # v.transaction_open?(:second) # -> false
213
- # v.gsub!(/you/, "world") # -> "Hello, world."
214
- #
215
- # v.start_transaction(:second) # -> ... (a Marshal string)
216
- # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
217
- # v.rewind_transaction(:first) # -> "Hello, you."
218
- # v.transaction_open? # -> true
219
- # v.transaction_open?(:first) # -> true
220
- # v.transaction_open?(:second) # -> false
221
- #
222
- # v.gsub!(/you/, "world") # -> "Hello, world."
223
- # v.start_transaction(:second) # -> ... (a Marshal string)
224
- # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
225
- # v.transaction_name # -> :second
226
- # v.abort_transaction(:first) # -> "Hello, you."
227
- # v.transaction_open? # -> false
228
- #
229
- # v.start_transaction(:first) # -> ... (a Marshal string)
230
- # v.gsub!(/you/, "world") # -> "Hello, world."
231
- # v.start_transaction(:second) # -> ... (a Marshal string)
232
- # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
233
- #
234
- # v.commit_transaction(:first) # -> "Hello, HAL."
235
- # v.transaction_open? # -> false
236
- #
237
- # == Thread Safety
238
- # Threadsafe version of Transaction::Simple and
239
- # Transaction::Simple::Group exist; these are loaded from
240
- # 'transaction/simple/threadsafe' and
241
- # 'transaction/simple/threadsafe/group', respectively, and are
242
- # represented in Ruby code as Transaction::Simple::ThreadSafe and
243
- # Transaction::Simple::ThreadSafe::Group, respectively.
244
- #
245
- # == Contraindications
246
- # While Transaction::Simple is very useful, it has some severe
247
- # limitations that must be understood. Transaction::Simple:
248
- #
249
- # * uses Marshal. Thus, any object which cannot be <i>Marshal</i>ed
250
- # cannot use Transaction::Simple. In my experience, this affects
251
- # singleton objects more often than any other object. It may be that
252
- # Ruby 2.0 will solve this problem.
253
- # * does not manage resources. Resources external to the object and its
254
- # instance variables are not managed at all. However, all instance
255
- # variables and objects "belonging" to those instance variables are
256
- # managed. If there are object reference counts to be handled,
257
- # Transaction::Simple will probably cause problems.
258
- # * is not inherently thread-safe. In the ACID ("atomic, consistent,
259
- # isolated, durable") test, Transaction::Simple provides CD, but it is
260
- # up to the user of Transaction::Simple to provide isolation and
261
- # atomicity. Transactions should be considered "critical sections" in
262
- # multi-threaded applications. If thread safety and atomicity is
263
- # absolutely required, use Transaction::Simple::ThreadSafe, which uses
264
- # a Mutex object to synchronize the accesses on the object during the
265
- # transaction operations.
266
- # * does not necessarily maintain Object#__id__ values on rewind or
267
- # abort. This may change for future versions that will be Ruby 1.8 or
268
- # better *only*. Certain objects that support #replace will maintain
269
- # Object#__id__.
270
- # * Can be a memory hog if you use many levels of transactions on many
271
- # objects.
272
- #
273
- module Simple
274
- TRANSACTION_SIMPLE_VERSION = '1.3.0'
275
-
276
- # Sets the Transaction::Simple debug object. It must respond to #<<.
277
- # Sets the transaction debug object. Debugging will be performed
278
- # automatically if there's a debug object. The generic transaction
279
- # error class.
280
- def self.debug_io=(io)
55
+ class << self
56
+ # Sets the Transaction::Simple debug object. It must respond to #<<.
57
+ # Debugging will be performed automatically if there's a debug object.
58
+ def debug_io=(io)
281
59
  if io.nil?
282
60
  @tdi = nil
283
61
  @debugging = false
284
62
  else
285
- unless io.respond_to?(:<<)
286
- raise TransactionError, Messages[:bad_debug_object]
287
- end
63
+ raise Transaction::TransactionError, Transaction::Messages[:bad_debug_object] unless io.respond_to?(:<<)
288
64
  @tdi = io
289
65
  @debugging = true
290
66
  end
291
67
  end
292
68
 
293
- # Returns +true+ if we are debugging.
294
- def self.debugging?
295
- @debugging
69
+ # Returns +true+ if we are debugging.
70
+ def debugging?
71
+ defined? @debugging and @debugging
296
72
  end
297
73
 
298
- # Returns the Transaction::Simple debug object. It must respond to
299
- # #<<.
300
- def self.debug_io
74
+ # Returns the Transaction::Simple debug object. It must respond to #<<.
75
+ def debug_io
301
76
  @tdi ||= ""
302
77
  @tdi
303
78
  end
79
+ end
304
80
 
305
- # If +name+ is +nil+ (default), then returns +true+ if there is
306
- # currently a transaction open.
307
- #
308
- # If +name+ is specified, then returns +true+ if there is currently a
309
- # transaction that responds to +name+ open.
310
- def transaction_open?(name = nil)
311
- if name.nil?
312
- if Transaction::Simple.debugging?
313
- Transaction::Simple.debug_io << "Transaction " <<
314
- "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
315
- end
316
- return (not @__transaction_checkpoint__.nil?)
317
- else
318
- if Transaction::Simple.debugging?
319
- Transaction::Simple.debug_io << "Transaction(#{name.inspect}) " <<
320
- "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
321
- end
322
- return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
323
- end
81
+ # If +name+ is +nil+ (default), then returns +true+ if there is currently
82
+ # a transaction open. If +name+ is specified, then returns +true+ if there
83
+ # is currently a transaction known as +name+ open.
84
+ def transaction_open?(name = nil)
85
+ defined? @__transaction_checkpoint__ or @__transaction_checkpoint__ = nil
86
+ if name.nil?
87
+ Transaction::Simple.debug_io << "Transaction " << "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" if Transaction::Simple.debugging?
88
+ return (not @__transaction_checkpoint__.nil?)
89
+ else
90
+ Transaction::Simple.debug_io << "Transaction(#{name.inspect}) " << "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" if Transaction::Simple.debugging?
91
+ return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
324
92
  end
93
+ end
325
94
 
326
- # Returns the current name of the transaction. Transactions not
327
- # explicitly named are named +nil+.
328
- def transaction_name
329
- if @__transaction_checkpoint__.nil?
330
- raise TransactionError, Messages[:no_transaction_open]
331
- end
332
- if Transaction::Simple.debugging?
333
- Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
334
- "Transaction Name: #{@__transaction_names__[-1].inspect}\n"
335
- end
336
- if @__transaction_names__[-1].kind_of?(String)
337
- @__transaction_names__[-1].dup
338
- else
339
- @__transaction_names__[-1]
340
- end
95
+ # Returns the current name of the transaction. Transactions not explicitly
96
+ # named are named +nil+.
97
+ def transaction_name
98
+ raise Transaction::TransactionError, Transaction::Messages[:no_transaction_open] if @__transaction_checkpoint__.nil?
99
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << "Transaction Name: #{@__transaction_names__[-1].inspect}\n" if Transaction::Simple.debugging?
100
+ if @__transaction_names__[-1].kind_of?(String)
101
+ @__transaction_names__[-1].dup
102
+ else
103
+ @__transaction_names__[-1]
341
104
  end
105
+ end
342
106
 
343
- # Starts a transaction. Stores the current object state. If a
344
- # transaction name is specified, the transaction will be named.
345
- # Transaction names must be unique. Transaction names of +nil+ will be
346
- # treated as unnamed transactions.
347
- def start_transaction(name = nil)
348
- @__transaction_level__ ||= 0
349
- @__transaction_names__ ||= []
107
+ # Starts a transaction. Stores the current object state. If a transaction
108
+ # name is specified, the transaction will be named. Transaction names must
109
+ # be unique. Transaction names of +nil+ will be treated as unnamed
110
+ # transactions.
111
+ def start_transaction(name = nil)
112
+ @__transaction_level__ ||= 0
113
+ @__transaction_names__ ||= []
350
114
 
351
- if name.nil?
352
- @__transaction_names__ << nil
353
- ss = "" if Transaction::Simple.debugging?
354
- else
355
- if @__transaction_names__.include?(name)
356
- raise TransactionError, Messages[:unique_names]
357
- end
358
- name = name.dup.freeze if name.kind_of?(String)
359
- @__transaction_names__ << name
360
- ss = "(#{name.inspect})" if Transaction::Simple.debugging?
361
- end
362
-
363
- @__transaction_level__ += 1
115
+ name = name.dup.freeze if name.kind_of?(String)
364
116
 
365
- if Transaction::Simple.debugging?
366
- Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} " <<
367
- "Start Transaction#{ss}\n"
368
- end
117
+ raise Transaction::TransactionError, Transaction::Messages[:unique_names] if name and @__transaction_names__.include?(name)
369
118
 
370
- @__transaction_checkpoint__ = Marshal.dump(self)
371
- end
119
+ @__transaction_names__ << name
120
+ @__transaction_level__ += 1
372
121
 
373
- # Rewinds the transaction. If +name+ is specified, then the
374
- # intervening transactions will be aborted and the named transaction
375
- # will be rewound. Otherwise, only the current transaction is rewound.
376
- def rewind_transaction(name = nil)
377
- if @__transaction_checkpoint__.nil?
378
- raise TransactionError, Messages[:cannot_rewind_no_transaction]
379
- end
122
+ if Transaction::Simple.debugging?
123
+ ss = "(#{name.inspect})"
124
+ ss = "" unless ss
380
125
 
381
- # Check to see if we are trying to rewind a transaction that is
382
- # outside of the current transaction block.
383
- if @__transaction_block__ and name
384
- nix = @__transaction_names__.index(name) + 1
385
- if nix < @__transaction_block__
386
- raise TransactionError, Messages[:cannot_rewind_transaction_before_block]
387
- end
388
- end
126
+ Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} " << "Start Transaction#{ss}\n"
127
+ end
389
128
 
390
- if name.nil?
391
- __rewind_this_transaction
392
- ss = "" if Transaction::Simple.debugging?
393
- else
394
- unless @__transaction_names__.include?(name)
395
- raise TransactionError, Messages[:cannot_rewind_named_transaction] % name.inspect
396
- end
397
- ss = "(#{name})" if Transaction::Simple.debugging?
129
+ @__transaction_checkpoint__ = Marshal.dump(self)
130
+ end
398
131
 
399
- while @__transaction_names__[-1] != name
400
- @__transaction_checkpoint__ = __rewind_this_transaction
401
- if Transaction::Simple.debugging?
402
- Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
403
- "Rewind Transaction#{ss}\n"
404
- end
405
- @__transaction_level__ -= 1
406
- @__transaction_names__.pop
407
- end
408
- __rewind_this_transaction
409
- end
410
- if Transaction::Simple.debugging?
411
- Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
412
- "Rewind Transaction#{ss}\n"
413
- end
414
- self
132
+ # Rewinds the transaction. If +name+ is specified, then the intervening
133
+ # transactions will be aborted and the named transaction will be rewound.
134
+ # Otherwise, only the current transaction is rewound.
135
+ #
136
+ # After each level of transaction is rewound, if the callback method
137
+ # #_post_transaction_rewind is defined, it will be called. It is intended
138
+ # to allow a complex self-referential graph to fix itself. The simplest
139
+ # way to explain this is with an example.
140
+ #
141
+ # class Child
142
+ # attr_accessor :parent
143
+ # end
144
+ #
145
+ # class Parent
146
+ # include Transaction::Simple
147
+ #
148
+ # attr_reader :children
149
+ # def initialize
150
+ # @children = []
151
+ # end
152
+ #
153
+ # def << child
154
+ # child.parent = self
155
+ # @children << child
156
+ # end
157
+ #
158
+ # def valid?
159
+ # @children.all? { |child| child.parent == self }
160
+ # end
161
+ # end
162
+ #
163
+ # parent = Parent.new
164
+ # parent << Child.new
165
+ # parent.start_transaction
166
+ # parent << Child.new
167
+ # parent.abort_transaction
168
+ # puts parent.valid? # => false
169
+ #
170
+ # This problem can be fixed by modifying the Parent class to include the
171
+ # #_post_transaction_rewind callback.
172
+ #
173
+ # class Parent
174
+ # # Reconnect the restored children to me, instead of to the bogus me
175
+ # # that was restored to them by Marshal::load.
176
+ # def _post_transaction_rewind
177
+ # @children.each { |child| child.parent = self }
178
+ # end
179
+ # end
180
+ #
181
+ # parent = Parent.new
182
+ # parent << Child.new
183
+ # parent.start_transaction
184
+ # parent << Child.new
185
+ # parent.abort_transaction
186
+ # puts parent.valid? # => true
187
+ def rewind_transaction(name = nil)
188
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_rewind_no_transaction] if @__transaction_checkpoint__.nil?
189
+
190
+ # Check to see if we are trying to rewind a transaction that is
191
+ # outside of the current transaction block.
192
+ defined? @__transaction_block__ or @__transaction_block__ = nil
193
+ if @__transaction_block__ and name
194
+ nix = @__transaction_names__.index(name) + 1
195
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_rewind_transaction_before_block] if nix < @__transaction_block__
415
196
  end
416
197
 
417
- # Aborts the transaction. Resets the object state to what it was
418
- # before the transaction was started and closes the transaction. If
419
- # +name+ is specified, then the intervening transactions and the named
420
- # transaction will be aborted. Otherwise, only the current transaction
421
- # is aborted.
422
- #
423
- # If the current or named transaction has been started by a block
424
- # (Transaction::Simple.start), then the execution of the block will be
425
- # halted with +break+ +self+.
426
- def abort_transaction(name = nil)
427
- if @__transaction_checkpoint__.nil?
428
- raise TransactionError, Messages[:cannot_abort_no_transaction]
198
+ if name.nil?
199
+ checkpoint = @__transaction_checkpoint__
200
+ __rewind_this_transaction
201
+ @__transaction_checkpoint__ = checkpoint
202
+ ss = "" if Transaction::Simple.debugging?
203
+ else
204
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_rewind_named_transaction] % name.inspect unless @__transaction_names__.include?(name)
205
+ ss = "(#{name})" if Transaction::Simple.debugging?
206
+
207
+ while @__transaction_names__[-1] != name
208
+ @__transaction_checkpoint__ = __rewind_this_transaction
209
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << "Rewind Transaction#{ss}\n" if Transaction::Simple.debugging?
210
+ @__transaction_level__ -= 1
211
+ @__transaction_names__.pop
429
212
  end
213
+ checkpoint = @__transaction_checkpoint__
214
+ __rewind_this_transaction
215
+ @__transaction_checkpoint__ = checkpoint
216
+ end
217
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << "Rewind Transaction#{ss}\n" if Transaction::Simple.debugging?
218
+ self
219
+ end
430
220
 
431
- # Check to see if we are trying to abort a transaction that is
432
- # outside of the current transaction block. Otherwise, raise
433
- # TransactionAborted if they are the same.
434
- if @__transaction_block__ and name
435
- nix = @__transaction_names__.index(name) + 1
436
- if nix < @__transaction_block__
437
- raise TransactionError, Messages[:cannot_abort_transaction_before_block]
438
- end
439
-
440
- raise TransactionAborted if @__transaction_block__ == nix
441
- end
221
+ # Aborts the transaction. Rewinds the object state to what it was before
222
+ # the transaction was started and closes the transaction. If +name+ is
223
+ # specified, then the intervening transactions and the named transaction
224
+ # will be aborted. Otherwise, only the current transaction is aborted.
225
+ #
226
+ # See #rewind_transaction for information about dealing with complex
227
+ # self-referential object graphs.
228
+ #
229
+ # If the current or named transaction has been started by a block
230
+ # (Transaction::Simple.start), then the execution of the block will be
231
+ # halted with +break+ +self+.
232
+ def abort_transaction(name = nil)
233
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_abort_no_transaction] if @__transaction_checkpoint__.nil?
234
+
235
+ # Check to see if we are trying to abort a transaction that is outside
236
+ # of the current transaction block. Otherwise, raise TransactionAborted
237
+ # if they are the same.
238
+ defined? @__transaction_block__ or @__transaction_block__ = nil
239
+ if @__transaction_block__ and name
240
+ nix = @__transaction_names__.index(name) + 1
241
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_abort_transaction_before_block] if nix < @__transaction_block__
242
+
243
+ raise Transaction::TransactionAborted if @__transaction_block__ == nix
244
+ end
442
245
 
443
- raise TransactionAborted if @__transaction_block__ == @__transaction_level__
246
+ raise Transaction::TransactionAborted if @__transaction_block__ == @__transaction_level__
444
247
 
445
- if name.nil?
446
- __abort_transaction(name)
447
- else
448
- unless @__transaction_names__.include?(name)
449
- raise TransactionError, Messages[:cannot_abort_named_transaction] % name.inspect
450
- end
451
- __abort_transaction(name) while @__transaction_names__.include?(name)
452
- end
453
- self
248
+ if name.nil?
249
+ __abort_transaction(name)
250
+ else
251
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_abort_named_transaction] % name.inspect unless @__transaction_names__.include?(name)
252
+ __abort_transaction(name) while @__transaction_names__.include?(name)
454
253
  end
455
254
 
456
- # If +name+ is +nil+ (default), the current transaction level is
457
- # closed out and the changes are committed.
458
- #
459
- # If +name+ is specified and +name+ is in the list of named
460
- # transactions, then all transactions are closed and committed until
461
- # the named transaction is reached.
462
- def commit_transaction(name = nil)
463
- if @__transaction_checkpoint__.nil?
464
- raise TransactionError, Messages[:cannot_commit_no_transaction]
465
- end
466
- @__transaction_block__ ||= nil
467
-
468
- # Check to see if we are trying to commit a transaction that is
469
- # outside of the current transaction block. Otherwise, raise
470
- # TransactionCommitted if they are the same.
471
- if @__transaction_block__ and name
472
- nix = @__transaction_names__.index(name) + 1
473
- if nix < @__transaction_block__
474
- raise TransactionError, Messages[:cannot_commit_transaction_before_block]
475
- end
255
+ self
256
+ end
476
257
 
477
- raise TransactionCommitted if @__transaction_block__ == nix
478
- end
258
+ # If +name+ is +nil+ (default), the current transaction level is closed
259
+ # out and the changes are committed.
260
+ #
261
+ # If +name+ is specified and +name+ is in the list of named transactions,
262
+ # then all transactions are closed and committed until the named
263
+ # transaction is reached.
264
+ def commit_transaction(name = nil)
265
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_commit_no_transaction] if @__transaction_checkpoint__.nil?
266
+ @__transaction_block__ ||= nil
267
+
268
+ # Check to see if we are trying to commit a transaction that is outside
269
+ # of the current transaction block. Otherwise, raise
270
+ # TransactionCommitted if they are the same.
271
+ if @__transaction_block__ and name
272
+ nix = @__transaction_names__.index(name) + 1
273
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_commit_transaction_before_block] if nix < @__transaction_block__
274
+
275
+ raise Transaction::TransactionCommitted if @__transaction_block__ == nix
276
+ end
479
277
 
480
- raise TransactionCommitted if @__transaction_block__ == @__transaction_level__
278
+ raise Transaction::TransactionCommitted if @__transaction_block__ == @__transaction_level__
481
279
 
482
- if name.nil?
483
- ss = "" if Transaction::Simple.debugging?
484
- __commit_transaction
485
- if Transaction::Simple.debugging?
486
- Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
487
- "Commit Transaction#{ss}\n"
488
- end
489
- else
490
- unless @__transaction_names__.include?(name)
491
- raise TransactionError, Messages[:cannot_commit_named_transaction] % name.inspect
492
- end
493
- ss = "(#{name})" if Transaction::Simple.debugging?
280
+ if name.nil?
281
+ ss = "" if Transaction::Simple.debugging?
282
+ __commit_transaction
283
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << "Commit Transaction#{ss}\n" if Transaction::Simple.debugging?
284
+ else
285
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_commit_named_transaction] % name.inspect unless @__transaction_names__.include?(name)
286
+ ss = "(#{name})" if Transaction::Simple.debugging?
494
287
 
495
- while @__transaction_names__[-1] != name
496
- if Transaction::Simple.debugging?
497
- Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
498
- "Commit Transaction#{ss}\n"
499
- end
500
- __commit_transaction
501
- end
502
- if Transaction::Simple.debugging?
503
- Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
504
- "Commit Transaction#{ss}\n"
505
- end
288
+ while @__transaction_names__[-1] != name
289
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << "Commit Transaction#{ss}\n" if Transaction::Simple.debugging?
506
290
  __commit_transaction
507
291
  end
508
-
509
- self
292
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << "Commit Transaction#{ss}\n" if Transaction::Simple.debugging?
293
+ __commit_transaction
510
294
  end
511
295
 
512
- # Alternative method for calling the transaction methods. An optional
513
- # name can be specified for named transaction support.
514
- #
515
- # #transaction(:start):: #start_transaction
516
- # #transaction(:rewind):: #rewind_transaction
517
- # #transaction(:abort):: #abort_transaction
518
- # #transaction(:commit):: #commit_transaction
519
- # #transaction(:name):: #transaction_name
520
- # #transaction:: #transaction_open?
521
- def transaction(action = nil, name = nil)
522
- case action
523
- when :start
524
- start_transaction(name)
525
- when :rewind
526
- rewind_transaction(name)
527
- when :abort
528
- abort_transaction(name)
529
- when :commit
530
- commit_transaction(name)
531
- when :name
532
- transaction_name
533
- when nil
534
- transaction_open?(name)
535
- end
296
+ self
297
+ end
298
+
299
+ # Alternative method for calling the transaction methods. An optional name
300
+ # can be specified for named transaction support. This method is
301
+ # deprecated and will be removed in Transaction::Simple 2.0.
302
+ #
303
+ # #transaction(:start):: #start_transaction
304
+ # #transaction(:rewind):: #rewind_transaction
305
+ # #transaction(:abort):: #abort_transaction
306
+ # #transaction(:commit):: #commit_transaction
307
+ # #transaction(:name):: #transaction_name
308
+ # #transaction:: #transaction_open?
309
+ def transaction(action = nil, name = nil)
310
+ _method = case action
311
+ when :start then :start_transaction
312
+ when :rewind then :rewind_transaction
313
+ when :abort then :abort_transaction
314
+ when :commit then :commit_transaction
315
+ when :name then :transaction_name
316
+ when nil then :transaction_open?
317
+ else nil
318
+ end
319
+
320
+ if method
321
+ warn "The #transaction method has been deprecated. Use #{method} instead."
322
+ else
323
+ warn "The #transaction method has been deprecated."
536
324
  end
537
325
 
538
- # Allows specific variables to be excluded from transaction support.
539
- # Must be done after extending the object but before starting the
540
- # first transaction on the object.
541
- #
542
- # vv.transaction_exclusions << "@io"
543
- def transaction_exclusions
544
- @transaction_exclusions ||= []
326
+ case method
327
+ when :transaction_name
328
+ __send__ method
329
+ when nil
330
+ nil
331
+ else
332
+ __send__ method, name
545
333
  end
334
+ end
546
335
 
547
- class << self
548
- def __common_start(name, vars, &block)
549
- if vars.empty?
550
- raise TransactionError, Messages[:cannot_start_empty_block_transaction]
551
- end
336
+ # Allows specific variables to be excluded from transaction support. Must
337
+ # be done after extending the object but before starting the first
338
+ # transaction on the object.
339
+ #
340
+ # vv.transaction_exclusions << "@io"
341
+ def transaction_exclusions
342
+ @transaction_exclusions ||= []
343
+ end
552
344
 
553
- if block
554
- begin
555
- vlevel = {}
345
+ class << self
346
+ def __common_start(name, vars, &block)
347
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_start_empty_block_transaction] if vars.empty?
556
348
 
557
- vars.each do |vv|
558
- vv.extend(Transaction::Simple)
559
- vv.start_transaction(name)
560
- vlevel[vv.__id__] = vv.instance_variable_get(:@__transaction_level__)
561
- vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__])
562
- end
349
+ if block
350
+ begin
351
+ vlevel = {}
352
+
353
+ vars.each do |vv|
354
+ vv.extend(Transaction::Simple)
355
+ vv.start_transaction(name)
356
+ vlevel[vv.__id__] = vv.instance_variable_get(:@__transaction_level__)
357
+ vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__])
358
+ end
563
359
 
564
- yield(*vars)
565
- rescue TransactionAborted
566
- vars.each do |vv|
567
- if name.nil? and vv.transaction_open?
568
- loop do
569
- tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
570
- vv.instance_variable_set(:@__transaction_block__, -1)
571
- break if tlevel < vlevel[vv.__id__]
572
- vv.abort_transaction if vv.transaction_open?
573
- end
574
- elsif vv.transaction_open?(name)
360
+ yield(*vars)
361
+ rescue Transaction::TransactionAborted
362
+ vars.each do |vv|
363
+ if name.nil? and vv.transaction_open?
364
+ loop do
365
+ tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
575
366
  vv.instance_variable_set(:@__transaction_block__, -1)
576
- vv.abort_transaction(name)
367
+ break if tlevel < vlevel[vv.__id__]
368
+ vv.abort_transaction if vv.transaction_open?
577
369
  end
370
+ elsif vv.transaction_open?(name)
371
+ vv.instance_variable_set(:@__transaction_block__, -1)
372
+ vv.abort_transaction(name)
578
373
  end
579
- rescue TransactionCommitted
580
- nil
581
- ensure
582
- vars.each do |vv|
583
- if name.nil? and vv.transaction_open?
584
- loop do
585
- tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
586
- break if tlevel < vlevel[vv.__id__]
587
- vv.instance_variable_set(:@__transaction_block__, -1)
588
- vv.commit_transaction if vv.transaction_open?
589
- end
590
- elsif vv.transaction_open?(name)
374
+ end
375
+ rescue Transaction::TransactionCommitted
376
+ nil
377
+ ensure
378
+ vars.each do |vv|
379
+ if name.nil? and vv.transaction_open?
380
+ loop do
381
+ tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
382
+ break if tlevel < vlevel[vv.__id__]
591
383
  vv.instance_variable_set(:@__transaction_block__, -1)
592
- vv.commit_transaction(name)
384
+ vv.commit_transaction if vv.transaction_open?
593
385
  end
386
+ elsif vv.transaction_open?(name)
387
+ vv.instance_variable_set(:@__transaction_block__, -1)
388
+ vv.commit_transaction(name)
594
389
  end
595
390
  end
596
- else
597
- vars.each do |vv|
598
- vv.extend(Transaction::Simple)
599
- vv.start_transaction(name)
600
- end
391
+ end
392
+ else
393
+ vars.each do |vv|
394
+ vv.extend(Transaction::Simple)
395
+ vv.start_transaction(name)
601
396
  end
602
397
  end
603
- private :__common_start
398
+ end
399
+ private :__common_start
604
400
 
605
- def start_named(name, *vars, &block)
606
- __common_start(name, vars, &block)
607
- end
401
+ # Start a named transaction in a block. The transaction will auto-commit
402
+ # when the block finishes.
403
+ def start_named(name, *vars, &block)
404
+ __common_start(name, vars, &block)
405
+ end
608
406
 
609
- def start(*vars, &block)
610
- __common_start(nil, vars, &block)
611
- end
407
+ # Start a named transaction in a block. The transaction will auto-commit
408
+ # when the block finishes.
409
+ def start(*vars, &block)
410
+ __common_start(nil, vars, &block)
612
411
  end
412
+ end
613
413
 
614
- def __abort_transaction(name = nil) #:nodoc:
615
- @__transaction_checkpoint__ = __rewind_this_transaction
414
+ def __abort_transaction(name = nil) #:nodoc:
415
+ @__transaction_checkpoint__ = __rewind_this_transaction
616
416
 
417
+ if Transaction::Simple.debugging?
617
418
  if name.nil?
618
- ss = "" if Transaction::Simple.debugging?
419
+ ss = ""
619
420
  else
620
- ss = "(#{name.inspect})" if Transaction::Simple.debugging?
421
+ ss = "(#{name.inspect})"
621
422
  end
622
423
 
623
- if Transaction::Simple.debugging?
624
- Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
625
- "Abort Transaction#{ss}\n"
626
- end
627
- @__transaction_level__ -= 1
628
- @__transaction_names__.pop
629
- if @__transaction_level__ < 1
630
- @__transaction_level__ = 0
631
- @__transaction_names__ = []
632
- end
424
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << "Abort Transaction#{ss}\n"
633
425
  end
634
426
 
635
- TRANSACTION_CHECKPOINT = "@__transaction_checkpoint__" #:nodoc:
636
- SKIP_TRANSACTION_VARS = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc:
427
+ @__transaction_level__ -= 1
428
+ @__transaction_names__.pop
429
+ if @__transaction_level__ < 1
430
+ @__transaction_level__ = 0
431
+ @__transaction_names__ = []
432
+ @__transaction_checkpoint__ = nil
433
+ end
434
+ end
637
435
 
638
- def __rewind_this_transaction #:nodoc:
639
- rr = Marshal.restore(@__transaction_checkpoint__)
436
+ SKIP_TRANSACTION_VARS = %w(@__transaction_checkpoint__ @__transaction_level__)
640
437
 
641
- begin
642
- self.replace(rr) if respond_to?(:replace)
643
- rescue
644
- nil
645
- end
438
+ def __rewind_this_transaction #:nodoc:
439
+ defined? @__transaction_checkpoint__ or @__transaction_checkpoint__ = nil
440
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_rewind_no_transaction] if @__transaction_checkpoint__.nil?
441
+ rr = Marshal.restore(@__transaction_checkpoint__)
646
442
 
647
- rr.instance_variables.each do |vv|
648
- next if SKIP_TRANSACTION_VARS.include?(vv)
649
- next if self.transaction_exclusions.include?(vv)
650
- if respond_to?(:instance_variable_get)
651
- instance_variable_set(vv, rr.instance_variable_get(vv))
652
- else
653
- instance_eval(%q|#{vv} = rr.instance_eval("#{vv}")|)
654
- end
655
- end
443
+ replace(rr) if respond_to?(:replace)
656
444
 
657
- new_ivar = instance_variables - rr.instance_variables - SKIP_TRANSACTION_VARS
658
- new_ivar.each do |vv|
659
- if respond_to?(:instance_variable_set)
660
- instance_variable_set(vv, nil)
661
- else
662
- instance_eval(%q|#{vv} = nil|)
663
- end
664
- end
445
+ iv = rr.instance_variables - SKIP_TRANSACTION_VARS - self.transaction_exclusions
446
+ iv.each do |vv|
447
+ next if self.transaction_exclusions.include?(vv)
665
448
 
666
- if respond_to?(:instance_variable_get)
667
- rr.instance_variable_get(TRANSACTION_CHECKPOINT)
668
- else
669
- rr.instance_eval(TRANSACTION_CHECKPOINT)
670
- end
449
+ instance_variable_set(vv, rr.instance_variable_get(vv))
671
450
  end
672
451
 
673
- def __commit_transaction #:nodoc:
674
- if respond_to?(:instance_variable_get)
675
- @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_variable_get(TRANSACTION_CHECKPOINT)
676
- else
677
- @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_eval(TRANSACTION_CHECKPOINT)
678
- end
452
+ rest = instance_variables - rr.instance_variables - SKIP_TRANSACTION_VARS - self.transaction_exclusions
453
+ rest.each do |vv|
454
+ remove_instance_variable(vv)
455
+ end
679
456
 
680
- @__transaction_level__ -= 1
681
- @__transaction_names__.pop
457
+ _post_transaction_rewind if respond_to?(:_post_transaction_rewind)
682
458
 
683
- if @__transaction_level__ < 1
684
- @__transaction_level__ = 0
685
- @__transaction_names__ = []
686
- end
687
- end
459
+ w, $-w = $-w, false # 20070203 OH is this very UGLY
460
+ res = rr.instance_variable_get(:@__transaction_checkpoint__)
461
+ $-w = w # 20070203 OH is this very UGLY
462
+ res
463
+ end
688
464
 
689
- private :__abort_transaction
690
- private :__rewind_this_transaction
691
- private :__commit_transaction
465
+ def __commit_transaction #:nodoc:
466
+ defined? @__transaction_checkpoint__ or @__transaction_checkpoint__ = nil
467
+ raise Transaction::TransactionError, Transaction::Messages[:cannot_commit_no_transaction] if @__transaction_checkpoint__.nil?
468
+ old = Marshal.restore(@__transaction_checkpoint__)
469
+ w, $-w = $-w, false # 20070203 OH is this very UGLY
470
+ @__transaction_checkpoint__ = old.instance_variable_get(:@__transaction_checkpoint__)
471
+ $-w = w # 20070203 OH is this very UGLY
472
+
473
+ @__transaction_level__ -= 1
474
+ @__transaction_names__.pop
475
+
476
+ if @__transaction_level__ < 1
477
+ @__transaction_level__ = 0
478
+ @__transaction_names__ = []
479
+ @__transaction_checkpoint__ = nil
480
+ end
692
481
  end
482
+
483
+ private :__abort_transaction
484
+ private :__rewind_this_transaction
485
+ private :__commit_transaction
693
486
  end