ardm-transactions 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 10896a2c7320a29dcc5ed6a1f64a228dd8c7df05
4
+ data.tar.gz: 3d7cc2c2e29e306e83f45f2b87f37b62b80e8b4f
5
+ SHA512:
6
+ metadata.gz: 5eb9144e58d1b7f58928ef452b652773fd4423c6bbcdba01189f690ea68fa6d48ffb982fa8cfc352765f60c26bca1f255da0942162db7ffa5998afa32d9542bc
7
+ data.tar.gz: 8788e5183a35cc4d3208d0cc601dca0482c6118ca195006803b7ca4d4c4b955a53b3d9686f847456e383f6a657f402ef009c22ca6fe770efa040e370b41a6f67
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,36 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## Rubinius
17
+ *.rbc
18
+
19
+ ## PROJECT::GENERAL
20
+ *.gem
21
+ coverage
22
+ rdoc
23
+ pkg
24
+ tmp
25
+ doc
26
+ log
27
+ .yardoc
28
+ measurements
29
+
30
+ ## BUNDLER
31
+ .bundle
32
+ Gemfile.*
33
+
34
+ ## PROJECT::SPECIFIC
35
+ spec/db/
36
+ spec/log/
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ sudo: false
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.5
7
+ - 2.2.0
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: 2.1.5
11
+ - rvm: 2.2.0
data/Gemfile ADDED
@@ -0,0 +1,47 @@
1
+ require 'pathname'
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ SOURCE = ENV.fetch('SOURCE', :git).to_sym
8
+ REPO_POSTFIX = SOURCE == :path ? '' : '.git'
9
+ DATAMAPPER = SOURCE == :path ? Pathname(__FILE__).dirname.parent : 'http://github.com/ar-dm'
10
+ DM_VERSION = '~> 1.2.0'
11
+ DO_VERSION = '~> 0.10.6'
12
+ DM_DO_ADAPTERS = %w[ sqlite postgres mysql oracle sqlserver ]
13
+ CURRENT_BRANCH = ENV.fetch('GIT_BRANCH', 'master')
14
+
15
+ gem 'ardm-core', DM_VERSION, SOURCE => "#{DATAMAPPER}/ardm-core#{REPO_POSTFIX}", :branch => CURRENT_BRANCH
16
+
17
+ group :datamapper do
18
+
19
+ adapters = ENV['ADAPTER'] || ENV['ADAPTERS']
20
+ adapters = adapters.to_s.tr(',', ' ').split.uniq - %w[ in_memory ]
21
+
22
+ if (do_adapters = DM_DO_ADAPTERS & adapters).any?
23
+ do_options = {}
24
+ do_options[:git] = "#{DATAMAPPER}/do#{REPO_POSTFIX}" if ENV['DO_GIT'] == 'true'
25
+
26
+ gem 'data_objects', DO_VERSION, do_options.dup
27
+
28
+ do_adapters.each do |adapter|
29
+ adapter = 'sqlite3' if adapter == 'sqlite'
30
+ gem "do_#{adapter}", DO_VERSION, do_options.dup
31
+ end
32
+
33
+ gem 'ardm-do-adapter', DM_VERSION, SOURCE => "#{DATAMAPPER}/ardm-do-adapter#{REPO_POSTFIX}", :branch => CURRENT_BRANCH
34
+ end
35
+
36
+ adapters.each do |adapter|
37
+ gem "ardm-#{adapter}-adapter", DM_VERSION, SOURCE => "#{DATAMAPPER}/ardm-#{adapter}-adapter#{REPO_POSTFIX}", :branch => CURRENT_BRANCH
38
+ end
39
+
40
+ plugins = ENV['PLUGINS'] || ENV['PLUGIN']
41
+ plugins = plugins.to_s.tr(',', ' ').split.push('ardm-migrations').uniq
42
+
43
+ plugins.each do |plugin|
44
+ gem plugin, DM_VERSION, SOURCE => "#{DATAMAPPER}/#{plugin}#{REPO_POSTFIX}", :branch => CURRENT_BRANCH
45
+ end
46
+
47
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 snusnu
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ = dm-transactions
2
+
3
+ This gem adds transaction support for datamapper. The currently supported adapters are
4
+
5
+ * postgres
6
+ * mysql
7
+ * sqlite3
8
+ * oracle
9
+ * sqlserver
10
+
11
+ == Note on Patches/Pull Requests
12
+
13
+ * Fork the project.
14
+ * Make your feature addition or bug fix.
15
+ * Add tests for it. This is important so I don't break it in a
16
+ future version unintentionally.
17
+ * Commit, do not mess with rakefile, version, or history.
18
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
19
+ * Send me a pull request. Bonus points for topic branches.
20
+
21
+ == Copyright
22
+
23
+ Copyright (c) 2011 snusnu. See LICENSE for details.
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ FileList['tasks/**/*.rake'].each { |task| import task }
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/dm-transactions/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "ardm-transactions"
6
+ gem.version = DataMapper::Transactions::VERSION
7
+
8
+ gem.authors = [ 'Martin Emde', 'Dirkjan Bussink (dbussink)', 'Dan Kubb (dkubb)' ]
9
+ gem.email = [ 'me@martinemde.com', "gamsnjaga@gmail.com" ]
10
+ gem.summary = "Ardm fork of dm-transactions"
11
+ gem.description = "Makes transaction support available for adapters that support them"
12
+ gem.homepage = "https://github.com/ar-dm/ardm-transactions"
13
+ gem.license = 'MIT'
14
+
15
+ gem.files = `git ls-files`.split("\n")
16
+ gem.test_files = `git ls-files -- {spec}/*`.split("\n")
17
+ gem.extra_rdoc_files = %w[LICENSE README.rdoc]
18
+
19
+ gem.require_paths = [ "lib" ]
20
+
21
+ gem.add_runtime_dependency 'ardm-core', '~> 1.2'
22
+
23
+ gem.add_development_dependency 'rake', '~> 0.9'
24
+ gem.add_development_dependency 'rspec', '~> 1.3'
25
+ end
@@ -0,0 +1 @@
1
+ require 'dm-transactions'
@@ -0,0 +1,443 @@
1
+ require 'dm-core'
2
+
3
+ module DataMapper
4
+ class Transaction
5
+ extend Chainable
6
+
7
+ # @api private
8
+ attr_accessor :state
9
+
10
+ # @api private
11
+ def none?
12
+ state == :none
13
+ end
14
+
15
+ # @api private
16
+ def begin?
17
+ state == :begin
18
+ end
19
+
20
+ # @api private
21
+ def rollback?
22
+ state == :rollback
23
+ end
24
+
25
+ # @api private
26
+ def commit?
27
+ state == :commit
28
+ end
29
+
30
+ # Create a new Transaction
31
+ #
32
+ # @see Transaction#link
33
+ #
34
+ # In fact, it just calls #link with the given arguments at the end of the
35
+ # constructor.
36
+ #
37
+ # @api public
38
+ def initialize(*things)
39
+ @transaction_primitives = {}
40
+ self.state = :none
41
+ @adapters = {}
42
+ link(*things)
43
+ if block_given?
44
+ warn "Passing block to #{self.class.name}.new is deprecated (#{caller[0]})"
45
+ commit { |*block_args| yield(*block_args) }
46
+ end
47
+ end
48
+
49
+ # Associate this Transaction with some things.
50
+ #
51
+ # @param [Object] things
52
+ # the things you want this Transaction associated with:
53
+ #
54
+ # Adapters::AbstractAdapter subclasses will be added as
55
+ # adapters as is.
56
+ # Arrays will have their elements added.
57
+ # Repository will have it's own @adapters added.
58
+ # Resource subclasses will have all the repositories of all
59
+ # their properties added.
60
+ # Resource instances will have all repositories of all their
61
+ # properties added.
62
+ #
63
+ # @param [Proc] block
64
+ # a block (taking one argument, the Transaction) to execute within
65
+ # this transaction. The transaction will begin and commit around
66
+ # the block, and rollback if an exception is raised.
67
+ #
68
+ # @api private
69
+ def link(*things)
70
+ unless none?
71
+ raise "Illegal state for link: #{state}"
72
+ end
73
+
74
+ things.each do |thing|
75
+ case thing
76
+ when DataMapper::Adapters::AbstractAdapter
77
+ @adapters[thing] = :none
78
+ when DataMapper::Repository
79
+ link(thing.adapter)
80
+ when DataMapper::Model
81
+ link(*thing.repositories)
82
+ when DataMapper::Resource
83
+ link(thing.model)
84
+ when Array
85
+ link(*thing)
86
+ else
87
+ raise "Unknown argument to #{self.class}#link: #{thing.inspect} (#{thing.class})"
88
+ end
89
+ end
90
+
91
+ if block_given?
92
+ commit { |*block_args| yield(*block_args) }
93
+ else
94
+ self
95
+ end
96
+ end
97
+
98
+ # Begin the transaction
99
+ #
100
+ # Before #begin is called, the transaction is not valid and can not be used.
101
+ #
102
+ # @api private
103
+ def begin
104
+ unless none?
105
+ raise "Illegal state for begin: #{state}"
106
+ end
107
+
108
+ each_adapter(:connect_adapter, [:log_fatal_transaction_breakage])
109
+ each_adapter(:begin_adapter, [:rollback_and_close_adapter_if_begin, :close_adapter_if_none])
110
+ self.state = :begin
111
+ end
112
+
113
+ # Commit the transaction
114
+ #
115
+ # If no block is given, it will simply commit any changes made since the
116
+ # Transaction did #begin.
117
+ #
118
+ # @param block<Block> a block (taking the one argument, the Transaction) to
119
+ # execute within this transaction. The transaction will begin and commit
120
+ # around the block, and roll back if an exception is raised.
121
+ #
122
+ # @api private
123
+ def commit
124
+ if block_given?
125
+ unless none?
126
+ raise "Illegal state for commit with block: #{state}"
127
+ end
128
+
129
+ begin
130
+ self.begin
131
+ rval = within { |*block_args| yield(*block_args) }
132
+ rescue Exception => exception
133
+ if begin?
134
+ rollback
135
+ end
136
+ raise exception
137
+ ensure
138
+ unless exception
139
+ if begin?
140
+ commit
141
+ end
142
+ return rval
143
+ end
144
+ end
145
+ else
146
+ unless begin?
147
+ raise "Illegal state for commit without block: #{state}"
148
+ end
149
+ each_adapter(:commit_adapter, [:log_fatal_transaction_breakage])
150
+ each_adapter(:close_adapter, [:log_fatal_transaction_breakage])
151
+ self.state = :commit
152
+ end
153
+ end
154
+
155
+ # Rollback the transaction
156
+ #
157
+ # Will undo all changes made during the transaction.
158
+ #
159
+ # @api private
160
+ def rollback
161
+ unless begin?
162
+ raise "Illegal state for rollback: #{state}"
163
+ end
164
+ each_adapter(:rollback_adapter_if_begin, [:rollback_and_close_adapter_if_begin, :close_adapter_if_none])
165
+ each_adapter(:close_adapter_if_open, [:log_fatal_transaction_breakage])
166
+ self.state = :rollback
167
+ end
168
+
169
+ # Execute a block within this Transaction.
170
+ #
171
+ # No #begin, #commit or #rollback is performed in #within, but this
172
+ # Transaction will pushed on the per thread stack of transactions for each
173
+ # adapter it is associated with, and it will ensures that it will pop the
174
+ # Transaction away again after the block is finished.
175
+ #
176
+ # @param block<Block> the block of code to execute.
177
+ #
178
+ # @api private
179
+ def within
180
+ unless block_given?
181
+ raise 'No block provided'
182
+ end
183
+
184
+ unless begin?
185
+ raise "Illegal state for within: #{state}"
186
+ end
187
+
188
+ adapters = @adapters
189
+
190
+ adapters.each_key do |adapter|
191
+ adapter.push_transaction(self)
192
+ end
193
+
194
+ begin
195
+ yield self
196
+ ensure
197
+ adapters.each_key do |adapter|
198
+ adapter.pop_transaction
199
+ end
200
+ end
201
+ end
202
+
203
+ # @api private
204
+ def method_missing(method, *args, &block)
205
+ first_arg = args.first
206
+
207
+ return super unless args.size == 1 && first_arg.kind_of?(Adapters::AbstractAdapter)
208
+ return super unless match = method.to_s.match(/\A(.*)_(if|unless)_(none|begin|rollback|commit)\z/)
209
+
210
+ action, condition, expected_state = match.captures
211
+ return super unless respond_to?(action, true)
212
+
213
+ state = state_for(first_arg).to_s
214
+ execute = (condition == 'if') == (state == expected_state)
215
+
216
+ send(action, first_arg) if execute
217
+ end
218
+
219
+ # @api private
220
+ def primitive_for(adapter)
221
+ unless @adapters.include?(adapter)
222
+ raise "Unknown adapter #{adapter}"
223
+ end
224
+
225
+ unless @transaction_primitives.include?(adapter)
226
+ raise "No primitive for #{adapter}"
227
+ end
228
+
229
+ @transaction_primitives[adapter]
230
+ end
231
+
232
+ private
233
+
234
+ # @api private
235
+ def validate_primitive(primitive)
236
+ [:close, :begin, :rollback, :commit].each do |meth|
237
+ unless primitive.respond_to?(meth)
238
+ raise "Invalid primitive #{primitive}: doesnt respond_to?(#{meth.inspect})"
239
+ end
240
+ end
241
+
242
+ primitive
243
+ end
244
+
245
+ # @api private
246
+ def each_adapter(method, on_fail)
247
+ adapters = @adapters
248
+ begin
249
+ adapters.each_key do |adapter|
250
+ send(method, adapter)
251
+ end
252
+ rescue Exception => exception
253
+ adapters.each_key do |adapter|
254
+ on_fail.each do |fail_handler|
255
+ begin
256
+ send(fail_handler, adapter)
257
+ rescue Exception => inner_exception
258
+ DataMapper.logger.fatal("#{self}#each_adapter(#{method.inspect}, #{on_fail.inspect}) failed with #{exception.inspect}: #{exception.backtrace.join("\n")} - and when sending #{fail_handler} to #{adapter} we failed again with #{inner_exception.inspect}: #{inner_exception.backtrace.join("\n")}")
259
+ end
260
+ end
261
+ end
262
+ raise exception
263
+ end
264
+ end
265
+
266
+ # @api private
267
+ def state_for(adapter)
268
+ unless @adapters.include?(adapter)
269
+ raise "Unknown adapter #{adapter}"
270
+ end
271
+
272
+ @adapters[adapter]
273
+ end
274
+
275
+ # @api private
276
+ def do_adapter(adapter, what, prerequisite)
277
+ unless @transaction_primitives.include?(adapter)
278
+ raise "No primitive for #{adapter}"
279
+ end
280
+
281
+ state = state_for(adapter)
282
+
283
+ unless state == prerequisite
284
+ raise "Illegal state for #{what}: #{state}"
285
+ end
286
+
287
+ DataMapper.logger.debug("#{adapter.name}: #{what}")
288
+ @transaction_primitives[adapter].send(what)
289
+ @adapters[adapter] = what
290
+ end
291
+
292
+ # @api private
293
+ def log_fatal_transaction_breakage(adapter)
294
+ DataMapper.logger.fatal("#{self} experienced a totally broken transaction execution. Presenting member #{adapter.inspect}.")
295
+ end
296
+
297
+ # @api private
298
+ def connect_adapter(adapter)
299
+ if @transaction_primitives.key?(adapter)
300
+ raise "Already a primitive for adapter #{adapter}"
301
+ end
302
+
303
+ @transaction_primitives[adapter] = validate_primitive(adapter.transaction_primitive)
304
+ end
305
+
306
+ # @api private
307
+ def close_adapter_if_open(adapter)
308
+ if @transaction_primitives.include?(adapter)
309
+ close_adapter(adapter)
310
+ end
311
+ end
312
+
313
+ # @api private
314
+ def close_adapter(adapter)
315
+ unless @transaction_primitives.include?(adapter)
316
+ raise 'No primitive for adapter'
317
+ end
318
+
319
+ @transaction_primitives[adapter].close
320
+ @transaction_primitives.delete(adapter)
321
+ end
322
+
323
+ # @api private
324
+ def begin_adapter(adapter)
325
+ do_adapter(adapter, :begin, :none)
326
+ end
327
+
328
+ # @api private
329
+ def commit_adapter(adapter)
330
+ do_adapter(adapter, :commit, :begin)
331
+ end
332
+
333
+ # @api private
334
+ def rollback_adapter(adapter)
335
+ do_adapter(adapter, :rollback, :begin)
336
+ end
337
+
338
+ # @api private
339
+ def rollback_and_close_adapter(adapter)
340
+ rollback_adapter(adapter)
341
+ close_adapter(adapter)
342
+ end
343
+
344
+ module Repository
345
+
346
+ # Produce a new Transaction for this Repository
347
+ #
348
+ # @return [Adapters::Transaction]
349
+ # a new Transaction (in state :none) that can be used
350
+ # to execute code #with_transaction
351
+ #
352
+ # @api public
353
+ def transaction
354
+ Transaction.new(self)
355
+ end
356
+ end # module Repository
357
+
358
+ module Model
359
+ # @api private
360
+ def self.included(mod)
361
+ mod.descendants.each { |model| model.extend self }
362
+ end
363
+
364
+ # Produce a new Transaction for this Resource class
365
+ #
366
+ # @return <Adapters::Transaction
367
+ # a new Adapters::Transaction with all Repositories
368
+ # of the class of this Resource added.
369
+ #
370
+ # @api public
371
+ def transaction
372
+ transaction = Transaction.new(self)
373
+ transaction.commit { |block_args| yield(*block_args) }
374
+ end
375
+ end # module Model
376
+
377
+ module Resource
378
+
379
+ # Produce a new Transaction for the class of this Resource
380
+ #
381
+ # @return [Adapters::Transaction]
382
+ # a new Adapters::Transaction for the Repository
383
+ # of the class of this Resource added.
384
+ #
385
+ # @api public
386
+ def transaction
387
+ model.transaction { |*block_args| yield(*block_args) }
388
+ end
389
+ end # module Resource
390
+
391
+ def self.include_transaction_api
392
+ [ :Repository, :Model, :Resource ].each do |name|
393
+ DataMapper.const_get(name).send(:include, const_get(name))
394
+ end
395
+ Adapters::AbstractAdapter.descendants.each do |adapter_class|
396
+ Adapters.include_transaction_api(DataMapper::Inflector.demodulize(adapter_class.name))
397
+ end
398
+ end
399
+
400
+ end # class Transaction
401
+
402
+ module Adapters
403
+
404
+ def self.include_transaction_api(const_name)
405
+ require transaction_extensions(const_name)
406
+ if Transaction.const_defined?(const_name)
407
+ adapter = const_get(const_name)
408
+ adapter.send(:include, transaction_module(const_name))
409
+ end
410
+ rescue LoadError
411
+ # Silently ignore the fact that no adapter extensions could be required
412
+ # This means that the adapter in use doesn't support transactions
413
+ end
414
+
415
+ def self.transaction_module(const_name)
416
+ Transaction.const_get(const_name)
417
+ end
418
+
419
+ class << self
420
+ private
421
+
422
+ # @api private
423
+ def transaction_extensions(const_name)
424
+ name = adapter_name(const_name)
425
+ name = 'do' if name == 'dataobjects'
426
+ "dm-transactions/adapters/dm-#{name}-adapter"
427
+ end
428
+
429
+ end
430
+
431
+ extendable do
432
+ # @api private
433
+ def const_added(const_name)
434
+ include_transaction_api(const_name)
435
+ super
436
+ end
437
+ end
438
+
439
+ end # module Adapters
440
+
441
+ Transaction.include_transaction_api
442
+
443
+ end # module DataMapper
@@ -0,0 +1,112 @@
1
+ module DataMapper
2
+ class Transaction
3
+
4
+ module DataObjectsAdapter
5
+ extend Chainable
6
+
7
+ # Produces a fresh transaction primitive for this Adapter
8
+ #
9
+ # Used by Transaction to perform its various tasks.
10
+ #
11
+ # @return [Object]
12
+ # a new Object that responds to :close, :begin, :commit,
13
+ # and :rollback,
14
+ #
15
+ # @api private
16
+ def transaction_primitive
17
+ if current_transaction && supports_savepoints?
18
+ DataObjects::SavePoint.create_for_uri(normalized_uri, current_connection)
19
+ else
20
+ DataObjects::Transaction.create_for_uri(normalized_uri)
21
+ end
22
+ end
23
+
24
+ # Pushes the given Transaction onto the per thread Transaction stack so
25
+ # that everything done by this Adapter is done within the context of said
26
+ # Transaction.
27
+ #
28
+ # @param [Transaction] transaction
29
+ # a Transaction to be the 'current' transaction until popped.
30
+ #
31
+ # @return [Array(Transaction)]
32
+ # the stack of active transactions for the current thread
33
+ #
34
+ # @api private
35
+ def push_transaction(transaction)
36
+ transactions << transaction
37
+ end
38
+
39
+ # Pop the 'current' Transaction from the per thread Transaction stack so
40
+ # that everything done by this Adapter is no longer necessarily within the
41
+ # context of said Transaction.
42
+ #
43
+ # @return [Transaction]
44
+ # the former 'current' transaction.
45
+ #
46
+ # @api private
47
+ def pop_transaction
48
+ transactions.pop
49
+ end
50
+
51
+ # Retrieve the current transaction for this Adapter.
52
+ #
53
+ # Everything done by this Adapter is done within the context of this
54
+ # Transaction.
55
+ #
56
+ # @return [Transaction]
57
+ # the 'current' transaction for this Adapter.
58
+ #
59
+ # @api private
60
+ def current_transaction
61
+ transactions.last
62
+ end
63
+
64
+ chainable do
65
+ protected
66
+
67
+ # @api semipublic
68
+ def open_connection
69
+ current_connection || super
70
+ end
71
+
72
+ # @api semipublic
73
+ def close_connection(connection)
74
+ super unless current_connection.equal?(connection)
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ # @api private
81
+ def transactions
82
+ Thread.current[:dm_transactions] ||= {}
83
+ Thread.current[:dm_transactions][object_id] ||= []
84
+ end
85
+
86
+ # Retrieve the current connection for this Adapter.
87
+ #
88
+ # @return [Transaction]
89
+ # the 'current' connection for this Adapter.
90
+ #
91
+ # @api private
92
+ def current_connection
93
+ if transaction = current_transaction
94
+ transaction.primitive_for(self).connection
95
+ end
96
+ end
97
+
98
+ # Indicate whether adapter supports transactional savepoints. Not all DO
99
+ # adapters do, so default to false.
100
+ #
101
+ # @return [Boolean]
102
+ # whether or not the adapter supports savepoints
103
+ #
104
+ # @api private
105
+ def supports_savepoints?
106
+ false
107
+ end
108
+
109
+ end # module DataObjectsAdapter
110
+
111
+ end # class Transaction
112
+ end # module DataMapper
@@ -0,0 +1,15 @@
1
+ require 'dm-transactions/adapters/dm-do-adapter'
2
+
3
+ module DataMapper
4
+ class Transaction
5
+
6
+ module MysqlAdapter
7
+ include DataObjectsAdapter
8
+
9
+ def supports_savepoints?
10
+ true
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ require 'dm-transactions/adapters/dm-do-adapter'
2
+
3
+ module DataMapper
4
+ class Transaction
5
+
6
+ module OracleAdapter
7
+ include DataObjectsAdapter
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ require 'dm-transactions/adapters/dm-do-adapter'
2
+
3
+ module DataMapper
4
+ class Transaction
5
+
6
+ module PostgresAdapter
7
+ include DataObjectsAdapter
8
+
9
+ def supports_savepoints?
10
+ true
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ require 'dm-transactions/adapters/dm-do-adapter'
2
+
3
+ module DataMapper
4
+ class Transaction
5
+
6
+ module SqliteAdapter
7
+ include DataObjectsAdapter
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'dm-transactions/adapters/dm-do-adapter'
2
+
3
+ module DataMapper
4
+ class Transaction
5
+
6
+ module SqlserverAdapter
7
+ include DataObjectsAdapter
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module DataMapper
2
+ module Transactions
3
+ VERSION = '1.2.0'
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec'
2
+ require 'isolated/require_spec'
3
+ require 'dm-core/spec/setup'
4
+
5
+ # To really test this behavior, this spec needs to be run in isolation and not
6
+ # as part of the typical rake spec run, which requires dm-transactions upfront
7
+
8
+ if %w[ postgres mysql sqlite oracle sqlserver ].include?(ENV['ADAPTER'])
9
+
10
+ describe "require 'dm-transactions after calling DataMapper.setup" do
11
+
12
+ before(:all) do
13
+ @adapter = DataMapper::Spec.adapter
14
+ require 'dm-transactions'
15
+ end
16
+
17
+ it_should_behave_like "require 'dm-transactions'"
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec'
2
+ require 'isolated/require_spec'
3
+ require 'dm-core/spec/setup'
4
+
5
+ # To really test this behavior, this spec needs to be run in isolation and not
6
+ # as part of the typical rake spec run, which requires dm-transactions upfront
7
+
8
+ if %w[ postgres mysql sqlite oracle sqlserver ].include?(ENV['ADAPTER'])
9
+
10
+ describe "require 'dm-transactions' before calling DataMapper.setup" do
11
+
12
+ before(:all) do
13
+ require 'dm-transactions'
14
+ @adapter = DataMapper::Spec.adapter
15
+ end
16
+
17
+ it_should_behave_like "require 'dm-transactions'"
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,15 @@
1
+ shared_examples_for "require 'dm-transactions'" do
2
+
3
+ %w[ Repository Model Resource ].each do |name|
4
+ it "should include the transaction api in DataMapper::#{name}" do
5
+ (DataMapper.const_get(name) < DataMapper::Transaction.const_get(name)).should be(true)
6
+ end
7
+ end
8
+
9
+ it "should include the transaction api into the adapter" do
10
+ @adapter.respond_to?(:push_transaction ).should be(true)
11
+ @adapter.respond_to?(:pop_transaction ).should be(true)
12
+ @adapter.respond_to?(:current_transaction).should be(true)
13
+ end
14
+
15
+ end
@@ -0,0 +1,222 @@
1
+ require 'spec_helper'
2
+
3
+ describe DataMapper::Resource, 'Transactions' do
4
+ before :all do
5
+ module ::Blog
6
+ class User
7
+ include DataMapper::Resource
8
+
9
+ property :name, String, :key => true
10
+ property :age, Integer
11
+ property :summary, Text
12
+ property :description, Text
13
+ property :admin, Boolean, :accessor => :private
14
+
15
+ belongs_to :parent, self, :required => false
16
+ has n, :children, self, :inverse => :parent
17
+
18
+ belongs_to :referrer, self, :required => false
19
+ has n, :comments
20
+
21
+ # FIXME: figure out a different approach than stubbing things out
22
+ def comment=(*)
23
+ # do nothing with comment
24
+ end
25
+ end
26
+
27
+ class Author < User; end
28
+
29
+ class Comment
30
+ include DataMapper::Resource
31
+
32
+ property :id, Serial
33
+ property :body, Text
34
+
35
+ belongs_to :user
36
+ end
37
+
38
+ class Article
39
+ include DataMapper::Resource
40
+
41
+ property :id, Serial
42
+ property :body, Text
43
+
44
+ has n, :paragraphs
45
+ end
46
+
47
+ class Paragraph
48
+ include DataMapper::Resource
49
+
50
+ property :id, Serial
51
+ property :text, String
52
+
53
+ belongs_to :article
54
+ end
55
+ end
56
+
57
+ class ::Default
58
+ include DataMapper::Resource
59
+
60
+ property :name, String, :key => true, :default => 'a default value'
61
+ end
62
+
63
+ @user_model = Blog::User
64
+ @author_model = Blog::Author
65
+ @comment_model = Blog::Comment
66
+ @article_model = Blog::Article
67
+ @paragraph_model = Blog::Paragraph
68
+ end
69
+
70
+ supported_by :postgres, :mysql, :sqlite, :oracle, :sqlserver do
71
+ before :all do
72
+ user = @user_model.create(:name => 'dbussink', :age => 25, :description => 'Test')
73
+
74
+ @user = @user_model.get(*user.key)
75
+ end
76
+
77
+ before do
78
+ # --- Temporary private api use to get around rspec limitations ---
79
+ @repository.scope do |repository|
80
+ transaction = DataMapper::Transaction.new(repository)
81
+ transaction.begin
82
+ repository.adapter.push_transaction(transaction)
83
+ end
84
+ end
85
+
86
+ after do
87
+ while @repository.adapter.current_transaction
88
+ @repository.adapter.pop_transaction.rollback
89
+ end
90
+ end
91
+
92
+ it_should_behave_like 'A public Resource'
93
+ it_should_behave_like 'A Resource supporting Strategic Eager Loading'
94
+ end
95
+
96
+ describe '#transaction' do
97
+ before :all do
98
+ class ::Author
99
+ include DataMapper::Resource
100
+
101
+ property :name, String, :key => true
102
+ end
103
+ end
104
+
105
+ supported_by :postgres, :mysql do
106
+ before do
107
+ @user_model.destroy!
108
+ end
109
+
110
+ it 'should support nested transactions' do
111
+ # can't assume dm-aggregates
112
+ begin
113
+ @user_model.all.length.should == 0
114
+
115
+ @user_model.transaction do # txn 1
116
+ @user_model.create(:name => 'jpr5')
117
+
118
+ @user_model.all.length.should == 1
119
+
120
+ begin
121
+ @user_model.transaction do # savepoint 1
122
+ @user_model.create(:name => 'dkubb')
123
+ @user_model.all.length.should == 2
124
+
125
+ raise
126
+ end
127
+ rescue => e
128
+ @user_model.all.length.should == 1
129
+ end
130
+
131
+ raise
132
+ end
133
+ rescue => e
134
+ @user_model.all.length.should == 0
135
+ end
136
+ end
137
+ end
138
+
139
+ supported_by :postgres, :mysql, :sqlite, :oracle, :sqlserver do
140
+ before do
141
+ @user_model.destroy!
142
+ end
143
+
144
+ it 'should have access to resources presisted before the transaction' do
145
+ @user_model.create(:name => 'carllerche')
146
+ @user_model.transaction do
147
+ @user_model.first.name.should == 'carllerche'
148
+ end
149
+ end
150
+
151
+ it 'should have access to resources persisted in the transaction' do
152
+ @user_model.transaction do
153
+ @user_model.create(:name => 'carllerche')
154
+ @user_model.first.name.should == 'carllerche'
155
+ end
156
+ end
157
+
158
+ it 'should not mark saved resources as new records' do
159
+ @user_model.transaction do
160
+ @user_model.create(:name => 'carllerche').should_not be_new
161
+ end
162
+ end
163
+
164
+ it 'should rollback when an error is raised in a transaction' do
165
+ @user_model.all.size.should == 0
166
+ lambda {
167
+ @user_model.transaction do
168
+ @user_model.create(:name => 'carllerche')
169
+ raise 'I love coffee'
170
+ end
171
+ }.should raise_error('I love coffee')
172
+ @user_model.all.size.should == 0
173
+ end
174
+
175
+ it 'should close the transaction if return is called within the closure' do
176
+ @txn = nil
177
+
178
+ def doit
179
+ @user_model.transaction do |transaction|
180
+ @txn = transaction
181
+ return
182
+ end
183
+ end
184
+ doit
185
+
186
+ @txn.instance_variable_get(:@state).should == :commit
187
+ @txn = nil
188
+ end
189
+
190
+ it 'should return the last statement in the transaction block' do
191
+ @user_model.transaction { 1 }.should == 1
192
+ end
193
+
194
+ with_alternate_adapter do
195
+ before :all do
196
+ class ::Article
197
+ include DataMapper::Resource
198
+
199
+ def self.default_repository_name
200
+ :alternate
201
+ end
202
+
203
+ property :title, String, :key => true
204
+ end
205
+
206
+ DataMapper.auto_migrate!(:alternate)
207
+ end
208
+
209
+ it 'should work with other repositories' do
210
+ expect {
211
+ DataMapper.repository.transaction.commit do
212
+ Author.create(:name => 'Dan Kubb')
213
+
214
+ # save a resource to another repository
215
+ Article.create(:title => 'DataMapper Rocks!')
216
+ end
217
+ }.should_not raise_error
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,6 @@
1
+ --exclude "spec,^/"
2
+ --sort coverage
3
+ --callsites
4
+ --xrefs
5
+ --profile
6
+ --text-summary
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,20 @@
1
+ require 'dm-transactions'
2
+
3
+ require 'dm-core/spec/setup'
4
+ require 'dm-core/spec/lib/spec_helper'
5
+ require 'dm-core/spec/lib/pending_helpers'
6
+ require 'dm-core/spec/lib/adapter_helpers'
7
+ require 'dm-core/spec/lib/counter_adapter'
8
+ require 'dm-core/spec/shared/resource_spec'
9
+ require 'dm-core/spec/shared/sel_spec'
10
+
11
+ Spec::Runner.configure do |config|
12
+
13
+ config.extend(DataMapper::Spec::Adapters::Helpers)
14
+ config.include(DataMapper::Spec::PendingHelpers)
15
+
16
+ config.after :all do
17
+ DataMapper::Spec.cleanup_models
18
+ end
19
+
20
+ end
@@ -0,0 +1,38 @@
1
+ spec_defaults = lambda do |spec|
2
+ spec.pattern = 'spec/**/*_spec.rb'
3
+ spec.libs << 'lib' << 'spec'
4
+ spec.spec_opts << '--options' << 'spec/spec.opts'
5
+ end
6
+
7
+ begin
8
+ require 'spec/rake/spectask'
9
+
10
+ Spec::Rake::SpecTask.new(:spec, &spec_defaults)
11
+ rescue LoadError
12
+ task :spec do
13
+ abort 'rspec is not available. In order to run spec, you must: gem install rspec'
14
+ end
15
+ end
16
+
17
+ begin
18
+ require 'rcov'
19
+ require 'spec/rake/verify_rcov'
20
+
21
+ Spec::Rake::SpecTask.new(:rcov) do |rcov|
22
+ spec_defaults.call(rcov)
23
+ rcov.rcov = true
24
+ rcov.rcov_opts = File.read('spec/rcov.opts').split(/\s+/)
25
+ end
26
+
27
+ RCov::VerifyTask.new(:verify_rcov => :rcov) do |rcov|
28
+ rcov.threshold = 100
29
+ end
30
+ rescue LoadError
31
+ %w[ rcov verify_rcov ].each do |name|
32
+ task name do
33
+ abort "rcov is not available. In order to run #{name}, you must: gem install rcov"
34
+ end
35
+ end
36
+ end
37
+
38
+ task :default => :spec
@@ -0,0 +1,9 @@
1
+ begin
2
+ require 'yard'
3
+
4
+ YARD::Rake::YardocTask.new
5
+ rescue LoadError
6
+ task :yard do
7
+ abort 'YARD is not available. In order to run yard, you must: gem install yard'
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'pathname'
3
+ require 'yardstick/rake/measurement'
4
+ require 'yardstick/rake/verify'
5
+
6
+ # yardstick_measure task
7
+ Yardstick::Rake::Measurement.new
8
+
9
+ # verify_measurements task
10
+ Yardstick::Rake::Verify.new do |verify|
11
+ verify.threshold = 100
12
+ end
13
+ rescue LoadError
14
+ %w[ yardstick_measure verify_measurements ].each do |name|
15
+ task name.to_s do
16
+ abort "Yardstick is not available. In order to run #{name}, you must: gem install yardstick"
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ardm-transactions
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Martin Emde
8
+ - Dirkjan Bussink (dbussink)
9
+ - Dan Kubb (dkubb)
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2015-01-29 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ardm-core
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.2'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rake
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '0.9'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '0.9'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rspec
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.3'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '1.3'
57
+ description: Makes transaction support available for adapters that support them
58
+ email:
59
+ - me@martinemde.com
60
+ - gamsnjaga@gmail.com
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files:
64
+ - LICENSE
65
+ - README.rdoc
66
+ files:
67
+ - ".document"
68
+ - ".gitignore"
69
+ - ".travis.yml"
70
+ - Gemfile
71
+ - LICENSE
72
+ - README.rdoc
73
+ - Rakefile
74
+ - ardm-transactions.gemspec
75
+ - lib/ardm-transactions.rb
76
+ - lib/dm-transactions.rb
77
+ - lib/dm-transactions/adapters/dm-do-adapter.rb
78
+ - lib/dm-transactions/adapters/dm-mysql-adapter.rb
79
+ - lib/dm-transactions/adapters/dm-oracle-adapter.rb
80
+ - lib/dm-transactions/adapters/dm-postgres-adapter.rb
81
+ - lib/dm-transactions/adapters/dm-sqlite-adapter.rb
82
+ - lib/dm-transactions/adapters/dm-sqlserver-adapter.rb
83
+ - lib/dm-transactions/version.rb
84
+ - spec/isolated/require_after_setup_spec.rb
85
+ - spec/isolated/require_before_setup_spec.rb
86
+ - spec/isolated/require_spec.rb
87
+ - spec/public/dm-transactions_spec.rb
88
+ - spec/rcov.opts
89
+ - spec/spec.opts
90
+ - spec/spec_helper.rb
91
+ - tasks/spec.rake
92
+ - tasks/yard.rake
93
+ - tasks/yardstick.rake
94
+ homepage: https://github.com/ar-dm/ardm-transactions
95
+ licenses:
96
+ - MIT
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 2.2.2
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: Ardm fork of dm-transactions
118
+ test_files: []
119
+ has_rdoc: