activerecord 1.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (106) hide show
  1. data/CHANGELOG +581 -0
  2. data/README +361 -0
  3. data/RUNNING_UNIT_TESTS +36 -0
  4. data/dev-utils/eval_debugger.rb +9 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/associations.rb +87 -0
  7. data/examples/shared_setup.rb +15 -0
  8. data/examples/validation.rb +88 -0
  9. data/install.rb +60 -0
  10. data/lib/active_record.rb +48 -0
  11. data/lib/active_record/aggregations.rb +165 -0
  12. data/lib/active_record/associations.rb +536 -0
  13. data/lib/active_record/associations/association_collection.rb +70 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -0
  15. data/lib/active_record/associations/has_many_association.rb +104 -0
  16. data/lib/active_record/base.rb +985 -0
  17. data/lib/active_record/callbacks.rb +337 -0
  18. data/lib/active_record/connection_adapters/abstract_adapter.rb +326 -0
  19. data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -0
  20. data/lib/active_record/connection_adapters/postgresql_adapter.rb +177 -0
  21. data/lib/active_record/connection_adapters/sqlite_adapter.rb +107 -0
  22. data/lib/active_record/deprecated_associations.rb +70 -0
  23. data/lib/active_record/fixtures.rb +172 -0
  24. data/lib/active_record/observer.rb +71 -0
  25. data/lib/active_record/reflection.rb +126 -0
  26. data/lib/active_record/support/class_attribute_accessors.rb +43 -0
  27. data/lib/active_record/support/class_inheritable_attributes.rb +37 -0
  28. data/lib/active_record/support/clean_logger.rb +10 -0
  29. data/lib/active_record/support/inflector.rb +70 -0
  30. data/lib/active_record/transactions.rb +102 -0
  31. data/lib/active_record/validations.rb +205 -0
  32. data/lib/active_record/vendor/mysql.rb +1117 -0
  33. data/lib/active_record/vendor/simple.rb +702 -0
  34. data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
  35. data/lib/active_record/wrappings.rb +59 -0
  36. data/rakefile +122 -0
  37. data/test/abstract_unit.rb +16 -0
  38. data/test/aggregations_test.rb +34 -0
  39. data/test/all.sh +8 -0
  40. data/test/associations_test.rb +477 -0
  41. data/test/base_test.rb +513 -0
  42. data/test/class_inheritable_attributes_test.rb +33 -0
  43. data/test/connections/native_mysql/connection.rb +24 -0
  44. data/test/connections/native_postgresql/connection.rb +24 -0
  45. data/test/connections/native_sqlite/connection.rb +24 -0
  46. data/test/deprecated_associations_test.rb +336 -0
  47. data/test/finder_test.rb +67 -0
  48. data/test/fixtures/accounts/signals37 +3 -0
  49. data/test/fixtures/accounts/unknown +2 -0
  50. data/test/fixtures/auto_id.rb +4 -0
  51. data/test/fixtures/column_name.rb +3 -0
  52. data/test/fixtures/companies/first_client +6 -0
  53. data/test/fixtures/companies/first_firm +4 -0
  54. data/test/fixtures/companies/second_client +6 -0
  55. data/test/fixtures/company.rb +37 -0
  56. data/test/fixtures/company_in_module.rb +33 -0
  57. data/test/fixtures/course.rb +3 -0
  58. data/test/fixtures/courses/java +2 -0
  59. data/test/fixtures/courses/ruby +2 -0
  60. data/test/fixtures/customer.rb +30 -0
  61. data/test/fixtures/customers/david +6 -0
  62. data/test/fixtures/db_definitions/mysql.sql +96 -0
  63. data/test/fixtures/db_definitions/mysql2.sql +4 -0
  64. data/test/fixtures/db_definitions/postgresql.sql +113 -0
  65. data/test/fixtures/db_definitions/postgresql2.sql +4 -0
  66. data/test/fixtures/db_definitions/sqlite.sql +85 -0
  67. data/test/fixtures/db_definitions/sqlite2.sql +4 -0
  68. data/test/fixtures/default.rb +2 -0
  69. data/test/fixtures/developer.rb +8 -0
  70. data/test/fixtures/developers/david +2 -0
  71. data/test/fixtures/developers/jamis +2 -0
  72. data/test/fixtures/developers_projects/david_action_controller +2 -0
  73. data/test/fixtures/developers_projects/david_active_record +2 -0
  74. data/test/fixtures/developers_projects/jamis_active_record +2 -0
  75. data/test/fixtures/entrant.rb +3 -0
  76. data/test/fixtures/entrants/first +3 -0
  77. data/test/fixtures/entrants/second +3 -0
  78. data/test/fixtures/entrants/third +3 -0
  79. data/test/fixtures/fixture_database.sqlite +0 -0
  80. data/test/fixtures/fixture_database_2.sqlite +0 -0
  81. data/test/fixtures/movie.rb +5 -0
  82. data/test/fixtures/movies/first +2 -0
  83. data/test/fixtures/movies/second +2 -0
  84. data/test/fixtures/project.rb +3 -0
  85. data/test/fixtures/projects/action_controller +2 -0
  86. data/test/fixtures/projects/active_record +2 -0
  87. data/test/fixtures/reply.rb +21 -0
  88. data/test/fixtures/subscriber.rb +5 -0
  89. data/test/fixtures/subscribers/first +2 -0
  90. data/test/fixtures/subscribers/second +2 -0
  91. data/test/fixtures/topic.rb +20 -0
  92. data/test/fixtures/topics/first +9 -0
  93. data/test/fixtures/topics/second +8 -0
  94. data/test/fixtures_test.rb +20 -0
  95. data/test/inflector_test.rb +104 -0
  96. data/test/inheritance_test.rb +125 -0
  97. data/test/lifecycle_test.rb +110 -0
  98. data/test/modules_test.rb +21 -0
  99. data/test/multiple_db_test.rb +46 -0
  100. data/test/pk_test.rb +57 -0
  101. data/test/reflection_test.rb +78 -0
  102. data/test/thread_safety_test.rb +33 -0
  103. data/test/transactions_test.rb +83 -0
  104. data/test/unconnected_test.rb +24 -0
  105. data/test/validations_test.rb +126 -0
  106. metadata +166 -0
@@ -0,0 +1,702 @@
1
+ # :title: Transaction::Simple
2
+ #
3
+ # == Licence
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to
7
+ # deal in the Software without restriction, including without limitation the
8
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9
+ # sell copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ # IN THE SOFTWARE.
22
+ #--
23
+ # Transaction::Simple
24
+ # Simple object transaction support for Ruby
25
+ # Version 1.11
26
+ #
27
+ # Copyright (c) 2003 Austin Ziegler
28
+ #
29
+ # $Id: simple.rb,v 1.2 2004/08/20 13:56:37 webster132 Exp $
30
+ #
31
+ # ==========================================================================
32
+ # Revision History ::
33
+ # YYYY.MM.DD Change ID Developer
34
+ # Description
35
+ # --------------------------------------------------------------------------
36
+ # 2003.07.29 Austin Ziegler
37
+ # Added debugging capabilities and VERSION string.
38
+ # 2003.08.21 Austin Ziegler
39
+ # Added named transactions.
40
+ #
41
+ # ==========================================================================
42
+ #++
43
+ require 'thread'
44
+
45
+ # The "Transaction" namespace can be used for additional transactional
46
+ # support objects and modules.
47
+ module Transaction
48
+
49
+ # A standard exception for transactional errors.
50
+ class TransactionError < StandardError; end
51
+ # A standard exception for transactional errors involving the acquisition
52
+ # of locks for Transaction::Simple::ThreadSafe.
53
+ class TransactionThreadError < StandardError; end
54
+
55
+ # = Transaction::Simple for Ruby
56
+ # Simple object transaction support for Ruby
57
+ #
58
+ # == Introduction
59
+ #
60
+ # Transaction::Simple provides a generic way to add active transactional
61
+ # support to objects. The transaction methods added by this module will
62
+ # work with most objects, excluding those that cannot be <i>Marshal</i>ed
63
+ # (bindings, procedure objects, IO instances, or singleton objects).
64
+ #
65
+ # The transactions supported by Transaction::Simple are not backed
66
+ # transactions; that is, they have nothing to do with any sort of data
67
+ # store. They are "live" transactions occurring in memory and in the
68
+ # object itself. This is to allow "test" changes to be made to an object
69
+ # before making the changes permanent.
70
+ #
71
+ # Transaction::Simple can handle an "infinite" number of transactional
72
+ # levels (limited only by memory). If I open two transactions, commit the
73
+ # first, but abort the second, the object will revert to the original
74
+ # version.
75
+ #
76
+ # Transaction::Simple supports "named" transactions, so that multiple
77
+ # levels of transactions can be committed, aborted, or rewound by
78
+ # referring to the appropriate name of the transaction. Names may be any
79
+ # object *except* +nil+.
80
+ #
81
+ # Copyright:: Copyright � 2003 by Austin Ziegler
82
+ # Version:: 1.1
83
+ # Licence:: MIT-Style
84
+ #
85
+ # Thanks to David Black for help with the initial concept that led to this
86
+ # library.
87
+ #
88
+ # == Usage
89
+ # include 'transaction/simple'
90
+ #
91
+ # v = "Hello, you." # => "Hello, you."
92
+ # v.extend(Transaction::Simple) # => "Hello, you."
93
+ #
94
+ # v.start_transaction # => ... (a Marshal string)
95
+ # v.transaction_open? # => true
96
+ # v.gsub!(/you/, "world") # => "Hello, world."
97
+ #
98
+ # v.rewind_transaction # => "Hello, you."
99
+ # v.transaction_open? # => true
100
+ #
101
+ # v.gsub!(/you/, "HAL") # => "Hello, HAL."
102
+ # v.abort_transaction # => "Hello, you."
103
+ # v.transaction_open? # => false
104
+ #
105
+ # v.start_transaction # => ... (a Marshal string)
106
+ # v.start_transaction # => ... (a Marshal string)
107
+ #
108
+ # v.transaction_open? # => true
109
+ # v.gsub!(/you/, "HAL") # => "Hello, HAL."
110
+ #
111
+ # v.commit_transaction # => "Hello, HAL."
112
+ # v.transaction_open? # => true
113
+ # v.abort_transaction # => "Hello, you."
114
+ # v.transaction_open? # => false
115
+ #
116
+ # == Named Transaction Usage
117
+ # v = "Hello, you." # => "Hello, you."
118
+ # v.extend(Transaction::Simple) # => "Hello, you."
119
+ #
120
+ # v.start_transaction(:first) # => ... (a Marshal string)
121
+ # v.transaction_open? # => true
122
+ # v.transaction_open?(:first) # => true
123
+ # v.transaction_open?(:second) # => false
124
+ # v.gsub!(/you/, "world") # => "Hello, world."
125
+ #
126
+ # v.start_transaction(:second) # => ... (a Marshal string)
127
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
128
+ # v.rewind_transaction(:first) # => "Hello, you."
129
+ # v.transaction_open? # => true
130
+ # v.transaction_open?(:first) # => true
131
+ # v.transaction_open?(:second) # => false
132
+ #
133
+ # v.gsub!(/you/, "world") # => "Hello, world."
134
+ # v.start_transaction(:second) # => ... (a Marshal string)
135
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
136
+ # v.transaction_name # => :second
137
+ # v.abort_transaction(:first) # => "Hello, you."
138
+ # v.transaction_open? # => false
139
+ #
140
+ # v.start_transaction(:first) # => ... (a Marshal string)
141
+ # v.gsub!(/you/, "world") # => "Hello, world."
142
+ # v.start_transaction(:second) # => ... (a Marshal string)
143
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
144
+ #
145
+ # v.commit_transaction(:first) # => "Hello, HAL."
146
+ # v.transaction_open? # => false
147
+ #
148
+ # == Contraindications
149
+ #
150
+ # While Transaction::Simple is very useful, it has some severe limitations
151
+ # that must be understood. Transaction::Simple:
152
+ #
153
+ # * uses Marshal. Thus, any object which cannot be <i>Marshal</i>ed cannot
154
+ # use Transaction::Simple.
155
+ # * does not manage resources. Resources external to the object and its
156
+ # instance variables are not managed at all. However, all instance
157
+ # variables and objects "belonging" to those instance variables are
158
+ # managed. If there are object reference counts to be handled,
159
+ # Transaction::Simple will probably cause problems.
160
+ # * is not inherently thread-safe. In the ACID ("atomic, consistent,
161
+ # isolated, durable") test, Transaction::Simple provides CD, but it is
162
+ # up to the user of Transaction::Simple to provide isolation and
163
+ # atomicity. Transactions should be considered "critical sections" in
164
+ # multi-threaded applications. If thread safety and atomicity is
165
+ # absolutely required, use Transaction::Simple::ThreadSafe, which uses a
166
+ # Mutex object to synchronize the accesses on the object during the
167
+ # transactional operations.
168
+ # * does not necessarily maintain Object#__id__ values on rewind or abort.
169
+ # This may change for future versions that will be Ruby 1.8 or better
170
+ # *only*. Certain objects that support #replace will maintain
171
+ # Object#__id__.
172
+ # * Can be a memory hog if you use many levels of transactions on many
173
+ # objects.
174
+ #
175
+ module Simple
176
+ VERSION = '1.1.1.0';
177
+
178
+ # Sets the Transaction::Simple debug object. It must respond to #<<.
179
+ # Sets the transaction debug object. Debugging will be performed
180
+ # automatically if there's a debug object. The generic transaction error
181
+ # class.
182
+ def self.debug_io=(io)
183
+ raise TransactionError, "Transaction Error: the transaction debug object must respond to #<<" unless io.respond_to?(:<<)
184
+ @tdi = io
185
+ end
186
+
187
+ # Returns the Transaction::Simple debug object. It must respond to #<<.
188
+ def self.debug_io
189
+ @tdi
190
+ end
191
+
192
+ # If +name+ is +nil+ (default), then returns +true+ if there is
193
+ # currently a transaction open.
194
+ #
195
+ # If +name+ is specified, then returns +true+ if there is currently a
196
+ # transaction that responds to +name+ open.
197
+ def transaction_open?(name = nil)
198
+ if name.nil?
199
+ Transaction::Simple.debug_io << "Transaction [#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" unless Transaction::Simple.debug_io.nil?
200
+ return (not @__transaction_checkpoint__.nil?)
201
+ else
202
+ Transaction::Simple.debug_io << "Transaction(#{name.inspect}) [#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" unless Transaction::Simple.debug_io.nil?
203
+ return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
204
+ end
205
+ end
206
+
207
+ # Returns the current name of the transaction. Transactions not
208
+ # explicitly named are named +nil+.
209
+ def transaction_name
210
+ raise TransactionError, "Transaction Error: No transaction open." if @__transaction_checkpoint__.nil?
211
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Transaction Name: #{@__transaction_names__[-1].inspect}\n" unless Transaction::Simple.debug_io.nil?
212
+ @__transaction_names__[-1]
213
+ end
214
+
215
+ # Starts a transaction. Stores the current object state. If a
216
+ # transaction name is specified, the transaction will be named.
217
+ # Transaction names must be unique. Transaction names of +nil+ will be
218
+ # treated as unnamed transactions.
219
+ def start_transaction(name = nil)
220
+ @__transaction_level__ ||= 0
221
+ @__transaction_names__ ||= []
222
+
223
+ if name.nil?
224
+ @__transaction_names__ << nil
225
+ s = ""
226
+ else
227
+ raise TransactionError, "Transaction Error: Named transactions must be unique." if @__transaction_names__.include?(name)
228
+ @__transaction_names__ << name
229
+ s = "(#{name.inspect})"
230
+ end
231
+
232
+ @__transaction_level__ += 1
233
+
234
+ Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} Start Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
235
+
236
+ @__transaction_checkpoint__ = Marshal.dump(self)
237
+ end
238
+
239
+ # Rewinds the transaction. If +name+ is specified, then the intervening
240
+ # transactions will be aborted and the named transaction will be
241
+ # rewound. Otherwise, only the current transaction is rewound.
242
+ def rewind_transaction(name = nil)
243
+ raise TransactionError, "Transaction Error: Cannot rewind. There is no current transaction." if @__transaction_checkpoint__.nil?
244
+ if name.nil?
245
+ __rewind_this_transaction
246
+ s = ""
247
+ else
248
+ raise TransactionError, "Transaction Error: Cannot rewind to transaction #{name.inspect} because it does not exist." unless @__transaction_names__.include?(name)
249
+ s = "(#{name})"
250
+
251
+ while @__transaction_names__[-1] != name
252
+ @__transaction_checkpoint__ = __rewind_this_transaction
253
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Rewind Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
254
+ @__transaction_level__ -= 1
255
+ @__transaction_names__.pop
256
+ end
257
+ __rewind_this_transaction
258
+ end
259
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Rewind Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
260
+ self
261
+ end
262
+
263
+ # Aborts the transaction. Resets the object state to what it was before
264
+ # the transaction was started and closes the transaction. If +name+ is
265
+ # specified, then the intervening transactions and the named transaction
266
+ # will be aborted. Otherwise, only the current transaction is aborted.
267
+ def abort_transaction(name = nil)
268
+ raise TransactionError, "Transaction Error: Cannot abort. There is no current transaction." if @__transaction_checkpoint__.nil?
269
+ if name.nil?
270
+ __abort_transaction(name)
271
+ else
272
+ raise TransactionError, "Transaction Error: Cannot abort nonexistant transaction #{name.inspect}." unless @__transaction_names__.include?(name)
273
+
274
+ __abort_transaction(name) while @__transaction_names__.include?(name)
275
+ end
276
+ self
277
+ end
278
+
279
+ # If +name+ is +nil+ (default), the current transaction level is closed
280
+ # out and the changes are committed.
281
+ #
282
+ # If +name+ is specified and +name+ is in the list of named
283
+ # transactions, then all transactions are closed and committed until the
284
+ # named transaction is reached.
285
+ def commit_transaction(name = nil)
286
+ raise TransactionError, "Transaction Error: Cannot commit. There is no current transaction." if @__transaction_checkpoint__.nil?
287
+
288
+ if name.nil?
289
+ s = ""
290
+ __commit_transaction
291
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
292
+ else
293
+ raise TransactionError, "Transaction Error: Cannot commit nonexistant transaction #{name.inspect}." unless @__transaction_names__.include?(name)
294
+ s = "(#{name})"
295
+
296
+ while @__transaction_names__[-1] != name
297
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
298
+ __commit_transaction
299
+ end
300
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
301
+ __commit_transaction
302
+ end
303
+ self
304
+ end
305
+
306
+ # Alternative method for calling the transaction methods. An optional
307
+ # name can be specified for named transaction support.
308
+ #
309
+ # #transaction(:start):: #start_transaction
310
+ # #transaction(:rewind):: #rewind_transaction
311
+ # #transaction(:abort):: #abort_transaction
312
+ # #transaction(:commit):: #commit_transaction
313
+ # #transaction(:name):: #transaction_name
314
+ # #transaction:: #transaction_open?
315
+ def transaction(action = nil, name = nil)
316
+ case action
317
+ when :start
318
+ start_transaction(name)
319
+ when :rewind
320
+ rewind_transaction(name)
321
+ when :abort
322
+ abort_transaction(name)
323
+ when :commit
324
+ commit_transaction(name)
325
+ when :name
326
+ transaction_name
327
+ when nil
328
+ transaction_open?(name)
329
+ end
330
+ end
331
+
332
+ def __abort_transaction(name = nil) #:nodoc:
333
+ @__transaction_checkpoint__ = __rewind_this_transaction
334
+
335
+ if name.nil?
336
+ s = ""
337
+ else
338
+ s = "(#{name.inspect})"
339
+ end
340
+
341
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Abort Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
342
+ @__transaction_level__ -= 1
343
+ @__transaction_names__.pop
344
+ if @__transaction_level__ < 1
345
+ @__transaction_level__ = 0
346
+ @__transaction_names__ = []
347
+ end
348
+ end
349
+
350
+ TRANSACTION_CHECKPOINT = "@__transaction_checkpoint__" #:nodoc:
351
+ SKIP_TRANSACTION_VARS = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc:
352
+
353
+ def __rewind_this_transaction #:nodoc:
354
+ r = Marshal.restore(@__transaction_checkpoint__)
355
+
356
+ begin
357
+ self.replace(r) if respond_to?(:replace)
358
+ rescue
359
+ nil
360
+ end
361
+
362
+ r.instance_variables.each do |i|
363
+ next if SKIP_TRANSACTION_VARS.include?(i)
364
+ if respond_to?(:instance_variable_get)
365
+ instance_variable_set(i, r.instance_variable_get(i))
366
+ else
367
+ instance_eval(%q|#{i} = r.instance_eval("#{i}")|)
368
+ end
369
+ end
370
+
371
+ if respond_to?(:instance_variable_get)
372
+ return r.instance_variable_get(TRANSACTION_CHECKPOINT)
373
+ else
374
+ return r.instance_eval(TRANSACTION_CHECKPOINT)
375
+ end
376
+ end
377
+
378
+ def __commit_transaction #:nodoc:
379
+ if respond_to?(:instance_variable_get)
380
+ @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_variable_get(TRANSACTION_CHECKPOINT)
381
+ else
382
+ @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_eval(TRANSACTION_CHECKPOINT)
383
+ end
384
+
385
+ @__transaction_level__ -= 1
386
+ @__transaction_names__.pop
387
+ if @__transaction_level__ < 1
388
+ @__transaction_level__ = 0
389
+ @__transaction_names__ = []
390
+ end
391
+ end
392
+
393
+ private :__abort_transaction, :__rewind_this_transaction, :__commit_transaction
394
+
395
+ # = Transaction::Simple::ThreadSafe
396
+ # Thread-safe simple object transaction support for Ruby.
397
+ # Transaction::Simple::ThreadSafe is used in the same way as
398
+ # Transaction::Simple. Transaction::Simple::ThreadSafe uses a Mutex
399
+ # object to ensure atomicity at the cost of performance in threaded
400
+ # applications.
401
+ #
402
+ # Transaction::Simple::ThreadSafe will not wait to obtain a lock; if the
403
+ # lock cannot be obtained immediately, a
404
+ # Transaction::TransactionThreadError will be raised.
405
+ #
406
+ # Thanks to Mauricio Fern�ndez for help with getting this part working.
407
+ module ThreadSafe
408
+ VERSION = '1.1.1.0';
409
+
410
+ include Transaction::Simple
411
+
412
+ SKIP_TRANSACTION_VARS = Transaction::Simple::SKIP_TRANSACTION_VARS.dup #:nodoc:
413
+ SKIP_TRANSACTION_VARS << "@__transaction_mutex__"
414
+
415
+ Transaction::Simple.instance_methods(false) do |meth|
416
+ next if meth == "transaction"
417
+ arg = "(name = nil)" unless meth == "transaction_name"
418
+ module_eval <<-EOS
419
+ def #{meth}#{arg}
420
+ if (@__transaction_mutex__ ||= Mutex.new).try_lock
421
+ result = super
422
+ @__transaction_mutex__.unlock
423
+ return result
424
+ else
425
+ raise TransactionThreadError, "Transaction Error: Cannot obtain lock for ##{meth}"
426
+ end
427
+ ensure
428
+ @__transaction_mutex__.unlock
429
+ end
430
+ EOS
431
+ end
432
+ end
433
+ end
434
+ end
435
+
436
+ if $0 == __FILE__
437
+ require 'test/unit'
438
+
439
+ class Test__Transaction_Simple < Test::Unit::TestCase #:nodoc:
440
+ VALUE = "Now is the time for all good men to come to the aid of their country."
441
+
442
+ def setup
443
+ @value = VALUE.dup
444
+ @value.extend(Transaction::Simple)
445
+ end
446
+
447
+ def test_extended
448
+ assert_respond_to(@value, :start_transaction)
449
+ end
450
+
451
+ def test_started
452
+ assert_equal(false, @value.transaction_open?)
453
+ assert_nothing_raised { @value.start_transaction }
454
+ assert_equal(true, @value.transaction_open?)
455
+ end
456
+
457
+ def test_rewind
458
+ assert_equal(false, @value.transaction_open?)
459
+ assert_raises(Transaction::TransactionError) { @value.rewind_transaction }
460
+ assert_nothing_raised { @value.start_transaction }
461
+ assert_equal(true, @value.transaction_open?)
462
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
463
+ assert_not_equal(VALUE, @value)
464
+ assert_nothing_raised { @value.rewind_transaction }
465
+ assert_equal(true, @value.transaction_open?)
466
+ assert_equal(VALUE, @value)
467
+ end
468
+
469
+ def test_abort
470
+ assert_equal(false, @value.transaction_open?)
471
+ assert_raises(Transaction::TransactionError) { @value.abort_transaction }
472
+ assert_nothing_raised { @value.start_transaction }
473
+ assert_equal(true, @value.transaction_open?)
474
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
475
+ assert_not_equal(VALUE, @value)
476
+ assert_nothing_raised { @value.abort_transaction }
477
+ assert_equal(false, @value.transaction_open?)
478
+ assert_equal(VALUE, @value)
479
+ end
480
+
481
+ def test_commit
482
+ assert_equal(false, @value.transaction_open?)
483
+ assert_raises(Transaction::TransactionError) { @value.commit_transaction }
484
+ assert_nothing_raised { @value.start_transaction }
485
+ assert_equal(true, @value.transaction_open?)
486
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
487
+ assert_not_equal(VALUE, @value)
488
+ assert_equal(true, @value.transaction_open?)
489
+ assert_nothing_raised { @value.commit_transaction }
490
+ assert_equal(false, @value.transaction_open?)
491
+ assert_not_equal(VALUE, @value)
492
+ end
493
+
494
+ def test_multilevel
495
+ assert_equal(false, @value.transaction_open?)
496
+ assert_nothing_raised { @value.start_transaction }
497
+ assert_equal(true, @value.transaction_open?)
498
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
499
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
500
+ assert_equal(true, @value.transaction_open?)
501
+ assert_nothing_raised { @value.start_transaction }
502
+ assert_nothing_raised { @value.gsub!(/country/, 'nation-state') }
503
+ assert_nothing_raised { @value.commit_transaction }
504
+ assert_equal(VALUE.gsub(/men/, 'women').gsub(/country/, 'nation-state'), @value)
505
+ assert_equal(true, @value.transaction_open?)
506
+ assert_nothing_raised { @value.abort_transaction }
507
+ assert_equal(VALUE, @value)
508
+ end
509
+
510
+ def test_multilevel_named
511
+ assert_equal(false, @value.transaction_open?)
512
+ assert_raises(Transaction::TransactionError) { @value.transaction_name }
513
+ assert_nothing_raised { @value.start_transaction(:first) } # 1
514
+ assert_raises(Transaction::TransactionError) { @value.start_transaction(:first) }
515
+ assert_equal(true, @value.transaction_open?)
516
+ assert_equal(true, @value.transaction_open?(:first))
517
+ assert_equal(:first, @value.transaction_name)
518
+ assert_nothing_raised { @value.start_transaction } # 2
519
+ assert_not_equal(:first, @value.transaction_name)
520
+ assert_equal(nil, @value.transaction_name)
521
+ assert_raises(Transaction::TransactionError) { @value.abort_transaction(:second) }
522
+ assert_nothing_raised { @value.abort_transaction(:first) }
523
+ assert_equal(false, @value.transaction_open?)
524
+ assert_nothing_raised do
525
+ @value.start_transaction(:first)
526
+ @value.gsub!(/men/, 'women')
527
+ @value.start_transaction(:second)
528
+ @value.gsub!(/women/, 'people')
529
+ @value.start_transaction
530
+ @value.gsub!(/people/, 'sentients')
531
+ end
532
+ assert_nothing_raised { @value.abort_transaction(:second) }
533
+ assert_equal(true, @value.transaction_open?(:first))
534
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
535
+ assert_nothing_raised do
536
+ @value.start_transaction(:second)
537
+ @value.gsub!(/women/, 'people')
538
+ @value.start_transaction
539
+ @value.gsub!(/people/, 'sentients')
540
+ end
541
+ assert_raises(Transaction::TransactionError) { @value.rewind_transaction(:foo) }
542
+ assert_nothing_raised { @value.rewind_transaction(:second) }
543
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
544
+ assert_nothing_raised do
545
+ @value.gsub!(/women/, 'people')
546
+ @value.start_transaction
547
+ @value.gsub!(/people/, 'sentients')
548
+ end
549
+ assert_raises(Transaction::TransactionError) { @value.commit_transaction(:foo) }
550
+ assert_nothing_raised { @value.commit_transaction(:first) }
551
+ assert_equal(VALUE.gsub(/men/, 'sentients'), @value)
552
+ assert_equal(false, @value.transaction_open?)
553
+ end
554
+
555
+ def test_array
556
+ assert_nothing_raised do
557
+ @orig = ["first", "second", "third"]
558
+ @value = ["first", "second", "third"]
559
+ @value.extend(Transaction::Simple)
560
+ end
561
+ assert_equal(@orig, @value)
562
+ assert_nothing_raised { @value.start_transaction }
563
+ assert_equal(true, @value.transaction_open?)
564
+ assert_nothing_raised { @value[1].gsub!(/second/, "fourth") }
565
+ assert_not_equal(@orig, @value)
566
+ assert_nothing_raised { @value.abort_transaction }
567
+ assert_equal(@orig, @value)
568
+ end
569
+ end
570
+
571
+ class Test__Transaction_Simple_ThreadSafe < Test::Unit::TestCase #:nodoc:
572
+ VALUE = "Now is the time for all good men to come to the aid of their country."
573
+
574
+ def setup
575
+ @value = VALUE.dup
576
+ @value.extend(Transaction::Simple::ThreadSafe)
577
+ end
578
+
579
+ def test_extended
580
+ assert_respond_to(@value, :start_transaction)
581
+ end
582
+
583
+ def test_started
584
+ assert_equal(false, @value.transaction_open?)
585
+ assert_nothing_raised { @value.start_transaction }
586
+ assert_equal(true, @value.transaction_open?)
587
+ end
588
+
589
+ def test_rewind
590
+ assert_equal(false, @value.transaction_open?)
591
+ assert_raises(Transaction::TransactionError) { @value.rewind_transaction }
592
+ assert_nothing_raised { @value.start_transaction }
593
+ assert_equal(true, @value.transaction_open?)
594
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
595
+ assert_not_equal(VALUE, @value)
596
+ assert_nothing_raised { @value.rewind_transaction }
597
+ assert_equal(true, @value.transaction_open?)
598
+ assert_equal(VALUE, @value)
599
+ end
600
+
601
+ def test_abort
602
+ assert_equal(false, @value.transaction_open?)
603
+ assert_raises(Transaction::TransactionError) { @value.abort_transaction }
604
+ assert_nothing_raised { @value.start_transaction }
605
+ assert_equal(true, @value.transaction_open?)
606
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
607
+ assert_not_equal(VALUE, @value)
608
+ assert_nothing_raised { @value.abort_transaction }
609
+ assert_equal(false, @value.transaction_open?)
610
+ assert_equal(VALUE, @value)
611
+ end
612
+
613
+ def test_commit
614
+ assert_equal(false, @value.transaction_open?)
615
+ assert_raises(Transaction::TransactionError) { @value.commit_transaction }
616
+ assert_nothing_raised { @value.start_transaction }
617
+ assert_equal(true, @value.transaction_open?)
618
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
619
+ assert_not_equal(VALUE, @value)
620
+ assert_equal(true, @value.transaction_open?)
621
+ assert_nothing_raised { @value.commit_transaction }
622
+ assert_equal(false, @value.transaction_open?)
623
+ assert_not_equal(VALUE, @value)
624
+ end
625
+
626
+ def test_multilevel
627
+ assert_equal(false, @value.transaction_open?)
628
+ assert_nothing_raised { @value.start_transaction }
629
+ assert_equal(true, @value.transaction_open?)
630
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
631
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
632
+ assert_equal(true, @value.transaction_open?)
633
+ assert_nothing_raised { @value.start_transaction }
634
+ assert_nothing_raised { @value.gsub!(/country/, 'nation-state') }
635
+ assert_nothing_raised { @value.commit_transaction }
636
+ assert_equal(VALUE.gsub(/men/, 'women').gsub(/country/, 'nation-state'), @value)
637
+ assert_equal(true, @value.transaction_open?)
638
+ assert_nothing_raised { @value.abort_transaction }
639
+ assert_equal(VALUE, @value)
640
+ end
641
+
642
+ def test_multilevel_named
643
+ assert_equal(false, @value.transaction_open?)
644
+ assert_raises(Transaction::TransactionError) { @value.transaction_name }
645
+ assert_nothing_raised { @value.start_transaction(:first) } # 1
646
+ assert_raises(Transaction::TransactionError) { @value.start_transaction(:first) }
647
+ assert_equal(true, @value.transaction_open?)
648
+ assert_equal(true, @value.transaction_open?(:first))
649
+ assert_equal(:first, @value.transaction_name)
650
+ assert_nothing_raised { @value.start_transaction } # 2
651
+ assert_not_equal(:first, @value.transaction_name)
652
+ assert_equal(nil, @value.transaction_name)
653
+ assert_raises(Transaction::TransactionError) { @value.abort_transaction(:second) }
654
+ assert_nothing_raised { @value.abort_transaction(:first) }
655
+ assert_equal(false, @value.transaction_open?)
656
+ assert_nothing_raised do
657
+ @value.start_transaction(:first)
658
+ @value.gsub!(/men/, 'women')
659
+ @value.start_transaction(:second)
660
+ @value.gsub!(/women/, 'people')
661
+ @value.start_transaction
662
+ @value.gsub!(/people/, 'sentients')
663
+ end
664
+ assert_nothing_raised { @value.abort_transaction(:second) }
665
+ assert_equal(true, @value.transaction_open?(:first))
666
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
667
+ assert_nothing_raised do
668
+ @value.start_transaction(:second)
669
+ @value.gsub!(/women/, 'people')
670
+ @value.start_transaction
671
+ @value.gsub!(/people/, 'sentients')
672
+ end
673
+ assert_raises(Transaction::TransactionError) { @value.rewind_transaction(:foo) }
674
+ assert_nothing_raised { @value.rewind_transaction(:second) }
675
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
676
+ assert_nothing_raised do
677
+ @value.gsub!(/women/, 'people')
678
+ @value.start_transaction
679
+ @value.gsub!(/people/, 'sentients')
680
+ end
681
+ assert_raises(Transaction::TransactionError) { @value.commit_transaction(:foo) }
682
+ assert_nothing_raised { @value.commit_transaction(:first) }
683
+ assert_equal(VALUE.gsub(/men/, 'sentients'), @value)
684
+ assert_equal(false, @value.transaction_open?)
685
+ end
686
+
687
+ def test_array
688
+ assert_nothing_raised do
689
+ @orig = ["first", "second", "third"]
690
+ @value = ["first", "second", "third"]
691
+ @value.extend(Transaction::Simple::ThreadSafe)
692
+ end
693
+ assert_equal(@orig, @value)
694
+ assert_nothing_raised { @value.start_transaction }
695
+ assert_equal(true, @value.transaction_open?)
696
+ assert_nothing_raised { @value[1].gsub!(/second/, "fourth") }
697
+ assert_not_equal(@orig, @value)
698
+ assert_nothing_raised { @value.abort_transaction }
699
+ assert_equal(@orig, @value)
700
+ end
701
+ end
702
+ end