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,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