ardm-transactions 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: