dm-transactions 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,11 @@
1
+ require 'dm-transactions/adapters/dm-do-adapter'
2
+
3
+ module DataMapper
4
+ class Transaction
5
+
6
+ module MysqlAdapter
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 OracleAdapter
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 PostgresAdapter
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 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,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
@@ -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 @@
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
@@ -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