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
@@ -0,0 +1,96 @@
|
|
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
|
+
DataObjects::Transaction.create_for_uri(normalized_uri)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Pushes the given Transaction onto the per thread Transaction stack so
|
21
|
+
# that everything done by this Adapter is done within the context of said
|
22
|
+
# Transaction.
|
23
|
+
#
|
24
|
+
# @param [Transaction] transaction
|
25
|
+
# a Transaction to be the 'current' transaction until popped.
|
26
|
+
#
|
27
|
+
# @return [Array(Transaction)]
|
28
|
+
# the stack of active transactions for the current thread
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
def push_transaction(transaction)
|
32
|
+
transactions << transaction
|
33
|
+
end
|
34
|
+
|
35
|
+
# Pop the 'current' Transaction from the per thread Transaction stack so
|
36
|
+
# that everything done by this Adapter is no longer necessarily within the
|
37
|
+
# context of said Transaction.
|
38
|
+
#
|
39
|
+
# @return [Transaction]
|
40
|
+
# the former 'current' transaction.
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
def pop_transaction
|
44
|
+
transactions.pop
|
45
|
+
end
|
46
|
+
|
47
|
+
# Retrieve the current transaction for this Adapter.
|
48
|
+
#
|
49
|
+
# Everything done by this Adapter is done within the context of this
|
50
|
+
# Transaction.
|
51
|
+
#
|
52
|
+
# @return [Transaction]
|
53
|
+
# the 'current' transaction for this Adapter.
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
def current_transaction
|
57
|
+
transactions.last
|
58
|
+
end
|
59
|
+
|
60
|
+
chainable do
|
61
|
+
protected
|
62
|
+
|
63
|
+
# @api semipublic
|
64
|
+
def open_connection
|
65
|
+
current_connection || super
|
66
|
+
end
|
67
|
+
|
68
|
+
# @api semipublic
|
69
|
+
def close_connection(connection)
|
70
|
+
super unless current_connection.equal?(connection)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# @api private
|
77
|
+
def transactions
|
78
|
+
Thread.current[:dm_transactions] ||= []
|
79
|
+
end
|
80
|
+
|
81
|
+
# Retrieve the current connection for this Adapter.
|
82
|
+
#
|
83
|
+
# @return [Transaction]
|
84
|
+
# the 'current' connection for this Adapter.
|
85
|
+
#
|
86
|
+
# @api private
|
87
|
+
def current_connection
|
88
|
+
if transaction = current_transaction
|
89
|
+
transaction.primitive_for(self).connection
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end # module DataObjectsAdapter
|
94
|
+
|
95
|
+
end # class Transaction
|
96
|
+
end # module DataMapper
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec'
|
2
|
+
require 'isolated/require_spec'
|
3
|
+
require 'dm-core/spec/setup'
|
4
|
+
require 'dm-core/spec/lib/adapter_helpers'
|
5
|
+
|
6
|
+
# To really test this behavior, this spec needs to be run in isolation and not
|
7
|
+
# as part of the typical rake spec run, which requires dm-transactions upfront
|
8
|
+
|
9
|
+
if %w[postgres mysql sqlite oracle sqlserver].include?(ENV['ADAPTER'])
|
10
|
+
|
11
|
+
describe "require 'dm-transactions after calling DataMapper.setup" do
|
12
|
+
extend DataMapper::Spec::Adapters::Helpers
|
13
|
+
|
14
|
+
before(:all) do
|
15
|
+
@adapter = DataMapper::Spec.adapter
|
16
|
+
require 'dm-transactions'
|
17
|
+
end
|
18
|
+
|
19
|
+
it_should_behave_like "require 'dm-transactions'"
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec'
|
2
|
+
require 'isolated/require_spec'
|
3
|
+
require 'dm-core/spec/setup'
|
4
|
+
require 'dm-core/spec/lib/adapter_helpers'
|
5
|
+
|
6
|
+
# To really test this behavior, this spec needs to be run in isolation and not
|
7
|
+
# as part of the typical rake spec run, which requires dm-transactions upfront
|
8
|
+
|
9
|
+
if %w[postgres mysql sqlite oracle sqlserver].include?(ENV['ADAPTER'])
|
10
|
+
|
11
|
+
describe "require 'dm-transactions' before calling DataMapper.setup" do
|
12
|
+
extend DataMapper::Spec::Adapters::Helpers
|
13
|
+
|
14
|
+
before(:all) do
|
15
|
+
require 'dm-transactions'
|
16
|
+
@adapter = DataMapper::Spec.adapter
|
17
|
+
end
|
18
|
+
|
19
|
+
it_should_behave_like "require 'dm-transactions'"
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
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,153 @@
|
|
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
|
+
supported_by :postgres, :mysql, :sqlite3, :oracle, :sqlserver do
|
97
|
+
describe '#transaction' do
|
98
|
+
before do
|
99
|
+
@user_model.all.destroy!
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should have access to resources presisted before the transaction' do
|
103
|
+
@user_model.create(:name => 'carllerche')
|
104
|
+
@user_model.transaction do
|
105
|
+
@user_model.first.name.should == 'carllerche'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should have access to resources persisted in the transaction' do
|
110
|
+
@user_model.transaction do
|
111
|
+
@user_model.create(:name => 'carllerche')
|
112
|
+
@user_model.first.name.should == 'carllerche'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should not mark saved resources as new records' do
|
117
|
+
@user_model.transaction do
|
118
|
+
@user_model.create(:name => 'carllerche').should_not be_new
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should rollback when an error is thrown in a transaction' do
|
123
|
+
@user_model.all.should have(0).entries
|
124
|
+
lambda {
|
125
|
+
@user_model.transaction do
|
126
|
+
@user_model.create(:name => 'carllerche')
|
127
|
+
raise 'I love coffee'
|
128
|
+
end
|
129
|
+
}.should raise_error('I love coffee')
|
130
|
+
@user_model.all.should have(0).entries
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should close the transaction if return is called within the closure' do
|
134
|
+
@txn = nil
|
135
|
+
|
136
|
+
def doit
|
137
|
+
@user_model.transaction do
|
138
|
+
@txn = Thread.current[:dm_transactions].last
|
139
|
+
return
|
140
|
+
end
|
141
|
+
end
|
142
|
+
doit
|
143
|
+
|
144
|
+
@txn.instance_variable_get(:@state).should == :commit
|
145
|
+
@txn = nil
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should return the last statement in the transaction block' do
|
149
|
+
@user_model.transaction { 1 }.should == 1
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
data/spec/rcov.opts
ADDED
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
data/tasks/ci.rake
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
task :ci => [ :verify_measurements, 'metrics:all' ]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
desc "Support bundling from local source code (allows BUNDLE_GEMFILE=Gemfile.local bundle foo)"
|
2
|
+
task :local_gemfile do |t|
|
3
|
+
|
4
|
+
root = Pathname(__FILE__).dirname.parent
|
5
|
+
datamapper = root.parent
|
6
|
+
|
7
|
+
source_regex = /DATAMAPPER = 'git:\/\/github.com\/datamapper'/
|
8
|
+
gem_source_regex = /:git => \"#\{DATAMAPPER\}\/(.+?)(?:\.git)?\"/
|
9
|
+
|
10
|
+
root.join('Gemfile.local').open('w') do |f|
|
11
|
+
root.join('Gemfile').open.each do |line|
|
12
|
+
line.sub!(source_regex, "DATAMAPPER = '#{datamapper}'")
|
13
|
+
line.sub!(gem_source_regex, ':path => "#{DATAMAPPER}/\1"')
|
14
|
+
f.puts line
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/tasks/metrics.rake
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
begin
|
2
|
+
require 'metric_fu'
|
3
|
+
rescue LoadError
|
4
|
+
namespace :metrics do
|
5
|
+
task :all do
|
6
|
+
abort 'metric_fu is not available. In order to run metrics:all, you must: gem install metric_fu'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'reek/adapters/rake_task'
|
13
|
+
|
14
|
+
Reek::RakeTask.new do |t|
|
15
|
+
t.fail_on_error = true
|
16
|
+
t.verbose = false
|
17
|
+
t.source_files = 'lib/**/*.rb'
|
18
|
+
end
|
19
|
+
rescue LoadError
|
20
|
+
task :reek do
|
21
|
+
abort 'Reek is not available. In order to run reek, you must: gem install reek'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
require 'roodi'
|
27
|
+
require 'roodi_task'
|
28
|
+
|
29
|
+
RoodiTask.new do |t|
|
30
|
+
t.verbose = false
|
31
|
+
end
|
32
|
+
rescue LoadError
|
33
|
+
task :roodi do
|
34
|
+
abort 'Roodi is not available. In order to run roodi, you must: gem install roodi'
|
35
|
+
end
|
36
|
+
end
|