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.
- data/.document +5 -0
- data/.gitignore +37 -0
- data/Gemfile +141 -0
- data/LICENSE +20 -0
- data/README.rdoc +23 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/dm-transactions.gemspec +78 -0
- data/lib/dm-transactions.rb +444 -0
- data/lib/dm-transactions/adapters/dm-do-adapter.rb +96 -0
- data/lib/dm-transactions/adapters/dm-mysql-adapter.rb +11 -0
- data/lib/dm-transactions/adapters/dm-oracle-adapter.rb +11 -0
- data/lib/dm-transactions/adapters/dm-postgres-adapter.rb +11 -0
- data/lib/dm-transactions/adapters/dm-sqlite-adapter.rb +11 -0
- data/lib/dm-transactions/adapters/dm-sqlserver-adapter.rb +11 -0
- data/spec/isolated/require_after_setup_spec.rb +23 -0
- data/spec/isolated/require_before_setup_spec.rb +23 -0
- data/spec/isolated/require_spec.rb +15 -0
- data/spec/public/dm-transactions_spec.rb +153 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +20 -0
- data/tasks/ci.rake +1 -0
- data/tasks/local_gemfile.rake +18 -0
- data/tasks/metrics.rake +36 -0
- data/tasks/spec.rake +38 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +125 -0
data/.document
ADDED
data/.gitignore
ADDED
@@ -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.
|
data/README.rdoc
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|