dm-transactions 1.0.0.rc1

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,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,37 @@
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.local
33
+ Gemfile.lock
34
+
35
+ ## PROJECT::SPECIFIC
36
+ spec/db/
37
+ spec/log/
data/Gemfile ADDED
@@ -0,0 +1,141 @@
1
+ # If you're working on more than one datamapper gem at a time, then it's
2
+ # recommended to create a local Gemfile and use this instead of the git
3
+ # sources. This will make sure that you are developing against your
4
+ # other local datamapper sources that you currently work on. Gemfile.local
5
+ # will behave identically to the standard Gemfile apart from the fact that
6
+ # it fetches the datamapper gems from local paths. This means that you can use
7
+ # the same environment variables, like ADAPTER(S) or PLUGIN(S) when running
8
+ # bundle commands. Gemfile.local is added to .gitignore, so you don't need to
9
+ # worry about accidentally checking local development paths into git.
10
+ # In order to create a local Gemfile, all you need to do is run:
11
+ #
12
+ # bundle exec rake local_gemfile
13
+ #
14
+ # This will give you a Gemfile.local file that points to your local clones of
15
+ # the various datamapper gems. It's assumed that all datamapper repo clones
16
+ # reside in the same directory. You can use the Gemfile.local like so for
17
+ # running any bundle command:
18
+ #
19
+ # BUNDLE_GEMFILE=Gemfile.local bundle foo
20
+ #
21
+ # You can also specify which adapter(s) should be part of the bundle by setting
22
+ # an environment variable. This of course also works when using the Gemfile.local
23
+ #
24
+ # bundle foo # dm-sqlite-adapter
25
+ # ADAPTER=mysql bundle foo # dm-mysql-adapter
26
+ # ADAPTERS=sqlite,mysql bundle foo # dm-sqlite-adapter and dm-mysql-adapter
27
+ #
28
+ # Of course you can also use the ADAPTER(S) variable when using the Gemfile.local
29
+ # and running specs against selected adapters.
30
+ #
31
+ # For easily working with adapters supported on your machine, it's recommended
32
+ # that you first install all adapters that you are planning to use or work on
33
+ # by doing something like
34
+ #
35
+ # ADAPTERS=sqlite,mysql,postgres bundle install
36
+ #
37
+ # This will clone the various repositories and make them available to bundler.
38
+ # Once you have them installed you can easily switch between adapters for the
39
+ # various development tasks. Running something like
40
+ #
41
+ # ADAPTER=mysql bundle exec rake spec
42
+ #
43
+ # will make sure that the dm-mysql-adapter is part of the bundle, and will be used
44
+ # when running the specs.
45
+ #
46
+ # You can also specify which plugin(s) should be part of the bundle by setting
47
+ # an environment variable. This also works when using the Gemfile.local
48
+ #
49
+ # bundle foo # dm-migrations
50
+ # PLUGINS=dm-validations bundle foo # dm-migrations and dm-validations
51
+ # PLUGINS=dm-validations,dm-types bundle foo # dm-migrations, dm-validations and dm-types
52
+ #
53
+ # Of course you can combine the PLUGIN(S) and ADAPTER(S) env vars to run specs
54
+ # for certain adapter/plugin combinations.
55
+ #
56
+ # Finally, to speed up running specs and other tasks, it's recommended to run
57
+ #
58
+ # bundle lock
59
+ #
60
+ # after running 'bundle install' for the first time. This will make 'bundle exec' run
61
+ # a lot faster compared to the unlocked version. With an unlocked bundle you would
62
+ # typically just run 'bundle install' from time to time to fetch the latest sources from
63
+ # upstream. When you locked your bundle, you need to run
64
+ #
65
+ # bundle install --relock
66
+ #
67
+ # to make sure to fetch the latest updates and then lock the bundle again. Gemfile.lock
68
+ # is added to the .gitignore file, so you don't need to worry about accidentally checking
69
+ # it into version control.
70
+
71
+ source 'http://rubygems.org'
72
+
73
+ DATAMAPPER = 'git://github.com/datamapper'
74
+ DM_VERSION = '~> 1.0.0.rc1'
75
+ DO_VERSION = '~> 0.10.2'
76
+
77
+ group :runtime do # Runtime dependencies (as in the gemspec)
78
+
79
+ if ENV['EXTLIB']
80
+ gem 'extlib', '~> 0.9.15', :git => "#{DATAMAPPER}/extlib.git"
81
+ else
82
+ gem 'activesupport', '~> 3.0.0.beta3', :git => 'git://github.com/rails/rails.git', :require => nil
83
+ end
84
+
85
+ gem 'dm-core', DM_VERSION, :git => "#{DATAMAPPER}/dm-core.git"
86
+
87
+ end
88
+
89
+ group(:development) do # Development dependencies (as in the gemspec)
90
+
91
+ gem 'rake', '~> 0.8.7'
92
+ gem 'rspec', '~> 1.3'
93
+ gem 'jeweler', '~> 1.4'
94
+
95
+ end
96
+
97
+ group :quality do # These gems contain rake tasks that check the quality of the source code
98
+
99
+ gem 'metric_fu', '~> 1.3'
100
+ gem 'rcov', '~> 0.9.7'
101
+ gem 'reek', '~> 1.2.7'
102
+ gem 'roodi', '~> 2.1'
103
+ gem 'yard', '~> 0.5'
104
+ gem 'yardstick', '~> 0.1'
105
+
106
+ end
107
+
108
+ group :datamapper do # We need this because we want to pin these dependencies to their git master sources
109
+
110
+ adapters = ENV['ADAPTER'] || ENV['ADAPTERS']
111
+ adapters = adapters.to_s.gsub(',',' ').split(' ') - ['in_memory']
112
+
113
+ unless adapters.empty?
114
+
115
+ DM_DO_ADAPTERS = %w[sqlite postgres mysql oracle sqlserver]
116
+
117
+ gem 'data_objects', DO_VERSION, :git => "#{DATAMAPPER}/do.git"
118
+
119
+ adapters.each do |adapter|
120
+ if DM_DO_ADAPTERS.any? { |dm_do_adapter| dm_do_adapter =~ /#{adapter}/ }
121
+ adapter = 'sqlite3' if adapter == 'sqlite'
122
+ gem "do_#{adapter}", DO_VERSION, :git => "#{DATAMAPPER}/do.git"
123
+ end
124
+ end
125
+
126
+ gem 'dm-do-adapter', DM_VERSION, :git => "#{DATAMAPPER}/dm-do-adapter.git"
127
+
128
+ adapters.each do |adapter|
129
+ gem "dm-#{adapter}-adapter", DM_VERSION, :git => "#{DATAMAPPER}/dm-#{adapter}-adapter.git"
130
+ end
131
+
132
+ end
133
+
134
+ plugins = ENV['PLUGINS'] || ENV['PLUGIN']
135
+ plugins = (plugins.to_s.gsub(',',' ').split(' ') + ['dm-migrations']).uniq
136
+
137
+ plugins.each do |plugin|
138
+ gem plugin, DM_VERSION, :git => "#{DATAMAPPER}/#{plugin}.git"
139
+ end
140
+
141
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 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) 2010 snusnu. See LICENSE for details.
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+
6
+ require 'jeweler'
7
+
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = "dm-transactions"
10
+ gem.summary = %Q{Adds transaction support to datamapper}
11
+ gem.description = %Q{Makes transaction support available for adapters that support them}
12
+ gem.email = "gamsnjaga@gmail.com"
13
+ gem.homepage = "http://github.com/datamapper/dm-transactions"
14
+ gem.authors = ["Dirkjan Bussink (dbussink)", "Dan Kubb (dkubb)"]
15
+
16
+ gem.add_dependency 'dm-core', '~> 1.0.0.rc1'
17
+
18
+ gem.add_development_dependency 'rspec', '~> 1.3'
19
+
20
+ end
21
+
22
+ Jeweler::GemcutterTasks.new
23
+
24
+ FileList['tasks/**/*.rake'].each { |task| import task }
25
+
26
+ rescue LoadError
27
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
28
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0.rc1
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{dm-transactions}
8
+ s.version = "1.0.0.rc1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Dirkjan Bussink (dbussink)", "Dan Kubb (dkubb)"]
12
+ s.date = %q{2010-05-19}
13
+ s.description = %q{Makes transaction support available for adapters that support them}
14
+ s.email = %q{gamsnjaga@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "Gemfile",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "dm-transactions.gemspec",
28
+ "lib/dm-transactions.rb",
29
+ "lib/dm-transactions/adapters/dm-do-adapter.rb",
30
+ "lib/dm-transactions/adapters/dm-mysql-adapter.rb",
31
+ "lib/dm-transactions/adapters/dm-oracle-adapter.rb",
32
+ "lib/dm-transactions/adapters/dm-postgres-adapter.rb",
33
+ "lib/dm-transactions/adapters/dm-sqlite-adapter.rb",
34
+ "lib/dm-transactions/adapters/dm-sqlserver-adapter.rb",
35
+ "spec/isolated/require_after_setup_spec.rb",
36
+ "spec/isolated/require_before_setup_spec.rb",
37
+ "spec/isolated/require_spec.rb",
38
+ "spec/public/dm-transactions_spec.rb",
39
+ "spec/rcov.opts",
40
+ "spec/spec.opts",
41
+ "spec/spec_helper.rb",
42
+ "tasks/ci.rake",
43
+ "tasks/local_gemfile.rake",
44
+ "tasks/metrics.rake",
45
+ "tasks/spec.rake",
46
+ "tasks/yard.rake",
47
+ "tasks/yardstick.rake"
48
+ ]
49
+ s.homepage = %q{http://github.com/datamapper/dm-transactions}
50
+ s.rdoc_options = ["--charset=UTF-8"]
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = %q{1.3.6}
53
+ s.summary = %q{Adds transaction support to datamapper}
54
+ s.test_files = [
55
+ "spec/isolated/require_after_setup_spec.rb",
56
+ "spec/isolated/require_before_setup_spec.rb",
57
+ "spec/isolated/require_spec.rb",
58
+ "spec/public/dm-transactions_spec.rb",
59
+ "spec/spec_helper.rb"
60
+ ]
61
+
62
+ if s.respond_to? :specification_version then
63
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
64
+ s.specification_version = 3
65
+
66
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
67
+ s.add_runtime_dependency(%q<dm-core>, ["~> 1.0.0.rc1"])
68
+ s.add_development_dependency(%q<rspec>, ["~> 1.3"])
69
+ else
70
+ s.add_dependency(%q<dm-core>, ["~> 1.0.0.rc1"])
71
+ s.add_dependency(%q<rspec>, ["~> 1.3"])
72
+ end
73
+ else
74
+ s.add_dependency(%q<dm-core>, ["~> 1.0.0.rc1"])
75
+ s.add_dependency(%q<rspec>, ["~> 1.3"])
76
+ end
77
+ end
78
+
@@ -0,0 +1,444 @@
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, Transaction.const_get(name))
394
+ end
395
+ DataMapper::Repository.adapters.values.each do |adapter|
396
+ Adapters.include_transaction_api(ActiveSupport::Inflector.demodulize(adapter.class.name))
397
+ end
398
+ end
399
+
400
+ end # class Transaction
401
+
402
+ module Adapters
403
+
404
+ class << self
405
+
406
+ def include_transaction_api(const_name)
407
+ require transaction_extensions(const_name)
408
+ adapter = const_get(const_name)
409
+ if DataMapper::Transaction.const_defined?(const_name)
410
+ adapter.send(:include, transaction_module(const_name))
411
+ end
412
+ rescue LoadError
413
+ # Silently ignore the fact that no adapter extensions could be required
414
+ # This means that the adapter in use doesn't support transactions
415
+ end
416
+
417
+ def transaction_module(const_name)
418
+ DataMapper::Transaction.const_get(const_name)
419
+ end
420
+
421
+ private
422
+
423
+ # @api private
424
+ def transaction_extensions(const_name)
425
+ name = adapter_name(const_name)
426
+ name = 'do' if name == 'dataobjects'
427
+ "dm-transactions/adapters/dm-#{name}-adapter"
428
+ end
429
+
430
+ end
431
+
432
+ extendable do
433
+ # @api private
434
+ def const_added(const_name)
435
+ include_transaction_api(const_name)
436
+ super
437
+ end
438
+ end
439
+
440
+ end # module Adapters
441
+
442
+ Transaction.include_transaction_api
443
+
444
+ end # module DataMapper