dm-core 0.10.1 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/.autotest +29 -0
  2. data/.document +5 -0
  3. data/.gitignore +27 -0
  4. data/LICENSE +20 -0
  5. data/{README.txt → README.rdoc} +14 -3
  6. data/Rakefile +23 -22
  7. data/VERSION +1 -0
  8. data/dm-core.gemspec +201 -10
  9. data/lib/dm-core.rb +32 -23
  10. data/lib/dm-core/adapters.rb +0 -1
  11. data/lib/dm-core/adapters/data_objects_adapter.rb +230 -151
  12. data/lib/dm-core/adapters/mysql_adapter.rb +7 -8
  13. data/lib/dm-core/adapters/oracle_adapter.rb +39 -59
  14. data/lib/dm-core/adapters/postgres_adapter.rb +0 -1
  15. data/lib/dm-core/adapters/sqlite3_adapter.rb +5 -0
  16. data/lib/dm-core/adapters/sqlserver_adapter.rb +114 -0
  17. data/lib/dm-core/adapters/yaml_adapter.rb +0 -5
  18. data/lib/dm-core/associations/many_to_many.rb +118 -56
  19. data/lib/dm-core/associations/many_to_one.rb +48 -21
  20. data/lib/dm-core/associations/one_to_many.rb +8 -30
  21. data/lib/dm-core/associations/one_to_one.rb +1 -5
  22. data/lib/dm-core/associations/relationship.rb +89 -97
  23. data/lib/dm-core/collection.rb +299 -184
  24. data/lib/dm-core/core_ext/enumerable.rb +28 -0
  25. data/lib/dm-core/core_ext/kernel.rb +0 -2
  26. data/lib/dm-core/migrations.rb +314 -170
  27. data/lib/dm-core/model.rb +97 -66
  28. data/lib/dm-core/model/descendant_set.rb +1 -1
  29. data/lib/dm-core/model/hook.rb +0 -3
  30. data/lib/dm-core/model/property.rb +7 -10
  31. data/lib/dm-core/model/relationship.rb +79 -26
  32. data/lib/dm-core/model/scope.rb +3 -4
  33. data/lib/dm-core/property.rb +152 -90
  34. data/lib/dm-core/property_set.rb +18 -37
  35. data/lib/dm-core/query.rb +452 -153
  36. data/lib/dm-core/query/conditions/comparison.rb +266 -173
  37. data/lib/dm-core/query/conditions/operation.rb +499 -57
  38. data/lib/dm-core/query/direction.rb +0 -3
  39. data/lib/dm-core/query/operator.rb +0 -4
  40. data/lib/dm-core/query/path.rb +10 -12
  41. data/lib/dm-core/query/sort.rb +4 -10
  42. data/lib/dm-core/repository.rb +10 -6
  43. data/lib/dm-core/resource.rb +343 -148
  44. data/lib/dm-core/spec/adapter_shared_spec.rb +17 -1
  45. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +277 -17
  46. data/lib/dm-core/support/chainable.rb +0 -2
  47. data/lib/dm-core/support/equalizer.rb +27 -3
  48. data/lib/dm-core/transaction.rb +75 -75
  49. data/lib/dm-core/type.rb +19 -5
  50. data/lib/dm-core/types/discriminator.rb +4 -4
  51. data/lib/dm-core/types/object.rb +2 -7
  52. data/lib/dm-core/types/paranoid_boolean.rb +8 -2
  53. data/lib/dm-core/types/paranoid_datetime.rb +8 -2
  54. data/lib/dm-core/version.rb +1 -1
  55. data/script/performance.rb +7 -7
  56. data/script/profile.rb +6 -6
  57. data/spec/lib/collection_helpers.rb +2 -2
  58. data/spec/lib/pending_helpers.rb +22 -3
  59. data/spec/lib/rspec_immediate_feedback_formatter.rb +1 -0
  60. data/spec/public/associations/many_to_many_spec.rb +6 -4
  61. data/spec/public/associations/many_to_one_spec.rb +10 -1
  62. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +39 -0
  63. data/spec/public/associations/one_to_many_spec.rb +4 -3
  64. data/spec/public/associations/one_to_one_spec.rb +19 -1
  65. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +45 -0
  66. data/spec/public/collection_spec.rb +4 -3
  67. data/spec/public/migrations_spec.rb +144 -0
  68. data/spec/public/model/relationship_spec.rb +115 -55
  69. data/spec/public/model_spec.rb +13 -13
  70. data/spec/public/property/object_spec.rb +106 -0
  71. data/spec/public/property_spec.rb +18 -14
  72. data/spec/public/resource_spec.rb +10 -1
  73. data/spec/public/sel_spec.rb +16 -49
  74. data/spec/public/setup_spec.rb +1 -1
  75. data/spec/public/shared/association_collection_shared_spec.rb +6 -14
  76. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  77. data/spec/public/shared/collection_shared_spec.rb +214 -217
  78. data/spec/public/shared/finder_shared_spec.rb +259 -365
  79. data/spec/public/shared/resource_shared_spec.rb +524 -248
  80. data/spec/public/transaction_spec.rb +27 -3
  81. data/spec/public/types/discriminator_spec.rb +1 -1
  82. data/spec/rcov.opts +6 -0
  83. data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +17 -0
  84. data/spec/semipublic/associations/many_to_one_spec.rb +3 -20
  85. data/spec/semipublic/associations_spec.rb +2 -2
  86. data/spec/semipublic/collection_spec.rb +0 -32
  87. data/spec/semipublic/model_spec.rb +96 -0
  88. data/spec/semipublic/property_spec.rb +3 -3
  89. data/spec/semipublic/query/conditions/comparison_spec.rb +1719 -0
  90. data/spec/semipublic/query/conditions/operation_spec.rb +1292 -0
  91. data/spec/semipublic/query_spec.rb +1285 -144
  92. data/spec/semipublic/resource_spec.rb +0 -24
  93. data/spec/semipublic/shared/resource_shared_spec.rb +103 -38
  94. data/spec/spec.opts +1 -1
  95. data/spec/spec_helper.rb +15 -6
  96. data/tasks/ci.rake +1 -0
  97. data/tasks/metrics.rake +37 -0
  98. data/tasks/spec.rake +41 -0
  99. data/tasks/yard.rake +9 -0
  100. data/tasks/yardstick.rake +19 -0
  101. metadata +99 -29
  102. data/CONTRIBUTING +0 -51
  103. data/FAQ +0 -93
  104. data/History.txt +0 -27
  105. data/MIT-LICENSE +0 -22
  106. data/Manifest.txt +0 -121
  107. data/QUICKLINKS +0 -11
  108. data/SPECS +0 -35
  109. data/TODO +0 -1
  110. data/spec/semipublic/query/conditions_spec.rb +0 -528
  111. data/tasks/ci.rb +0 -24
  112. data/tasks/dm.rb +0 -58
  113. data/tasks/doc.rb +0 -17
  114. data/tasks/gemspec.rb +0 -23
  115. data/tasks/hoe.rb +0 -45
  116. data/tasks/install.rb +0 -18
@@ -173,6 +173,21 @@ share_examples_for 'An Adapter' do
173
173
  Heffalump.all(:color.not => nil).should_not be_include(@two)
174
174
  end
175
175
 
176
+ it 'should be able to search for object with a nil value using required properties' do
177
+ Heffalump.all(:id.not => nil).should == [ @red, @two, @five ]
178
+ end
179
+
180
+ it 'should be able to search for objects not in an empty list (match all)' do
181
+ Heffalump.all(:color.not => []).should == [ @red, @two, @five ]
182
+ end
183
+
184
+ it 'should be able to search for objects in an empty list and another OR condition (match none on the empty list)' do
185
+ Heffalump.all(:conditions => DataMapper::Query::Conditions::Operation.new(
186
+ :or,
187
+ DataMapper::Query::Conditions::Comparison.new(:in, Heffalump.properties[:color], []),
188
+ DataMapper::Query::Conditions::Comparison.new(:in, Heffalump.properties[:num_spots], [5]))).should == [ @five ]
189
+ end
190
+
176
191
  it 'should be able to search for objects not included in an array of values' do
177
192
  Heffalump.all(:num_spots.not => [ 1, 3, 5, 7 ]).should be_include(@two)
178
193
  end
@@ -210,7 +225,8 @@ share_examples_for 'An Adapter' do
210
225
 
211
226
  describe 'regexp' do
212
227
  before do
213
- if defined?(DataMapper::Adapters::Sqlite3Adapter) && @adapter.kind_of?(DataMapper::Adapters::Sqlite3Adapter)
228
+ if (defined?(DataMapper::Adapters::Sqlite3Adapter) && @adapter.kind_of?(DataMapper::Adapters::Sqlite3Adapter) ||
229
+ defined?(DataMapper::Adapters::SqlserverAdapter) && @adapter.kind_of?(DataMapper::Adapters::SqlserverAdapter))
214
230
  pending 'delegate regexp matches to same system that the InMemory and YAML adapters use'
215
231
  end
216
232
  end
@@ -9,6 +9,13 @@ share_examples_for 'A DataObjects Adapter' do
9
9
 
10
10
  # set up the adapter after switching the logger so queries can be captured
11
11
  @adapter = DataMapper.setup(@adapter.name, @adapter.options)
12
+
13
+ @jruby = !!(RUBY_PLATFORM =~ /java/)
14
+
15
+ @postgres = defined?(DataMapper::Adapters::PostgresAdapter) && @adapter.kind_of?(DataMapper::Adapters::PostgresAdapter)
16
+ @mysql = defined?(DataMapper::Adapters::MysqlAdapter) && @adapter.kind_of?(DataMapper::Adapters::MysqlAdapter)
17
+ @sql_server = defined?(DataMapper::Adapters::SqlserverAdapter) && @adapter.kind_of?(DataMapper::Adapters::SqlserverAdapter)
18
+ @oracle = defined?(DataMapper::Adapters::OracleAdapter) && @adapter.kind_of?(DataMapper::Adapters::OracleAdapter)
12
19
  end
13
20
 
14
21
  after :all do
@@ -22,7 +29,7 @@ share_examples_for 'A DataObjects Adapter' do
22
29
 
23
30
  def log_output
24
31
  @log.rewind
25
- @log.read.chomp.gsub(/^\s+~ \(\d+\.?\d*\)\s+/, '')
32
+ @log.read.chomp.gsub(/^\s+~ \(\d+\.?\d*\)\s+/, '').split("\n")
26
33
  end
27
34
 
28
35
  def supports_default_values?
@@ -40,11 +47,8 @@ share_examples_for 'A DataObjects Adapter' do
40
47
  include DataMapper::Resource
41
48
 
42
49
  property :id, Serial
43
- end
44
50
 
45
- # create all tables and constraints before each spec
46
- if @repository.respond_to?(:auto_migrate!)
47
- Article.auto_migrate!
51
+ auto_migrate!
48
52
  end
49
53
 
50
54
  reset_log
@@ -53,8 +57,10 @@ share_examples_for 'A DataObjects Adapter' do
53
57
  end
54
58
 
55
59
  it 'should not send NULL values' do
56
- statement = if defined?(DataMapper::Adapters::MysqlAdapter) && @adapter.kind_of?(DataMapper::Adapters::MysqlAdapter)
60
+ statement = if @mysql
57
61
  /\AINSERT INTO `articles` \(\) VALUES \(\)\z/
62
+ elsif @oracle
63
+ /\AINSERT INTO "ARTICLES" \("ID"\) VALUES \(DEFAULT\) RETURNING "ID"/
58
64
  elsif supports_default_values? && supports_returning?
59
65
  /\AINSERT INTO "articles" DEFAULT VALUES RETURNING \"id\"\z/
60
66
  elsif supports_default_values?
@@ -63,7 +69,7 @@ share_examples_for 'A DataObjects Adapter' do
63
69
  /\AINSERT INTO "articles" \(\) VALUES \(\)\z/
64
70
  end
65
71
 
66
- log_output.should =~ statement
72
+ log_output.first.should =~ statement
67
73
  end
68
74
  end
69
75
 
@@ -74,11 +80,8 @@ share_examples_for 'A DataObjects Adapter' do
74
80
 
75
81
  property :id, Serial
76
82
  property :title, String
77
- end
78
83
 
79
- # create all tables and constraints before each spec
80
- if @repository.respond_to?(:auto_migrate!)
81
- Article.auto_migrate!
84
+ auto_migrate!
82
85
  end
83
86
 
84
87
  reset_log
@@ -87,20 +90,277 @@ share_examples_for 'A DataObjects Adapter' do
87
90
  end
88
91
 
89
92
  it 'should not send NULL values' do
90
- if defined?(DataMapper::Adapters::MysqlAdapter) && @adapter.kind_of?(DataMapper::Adapters::MysqlAdapter)
91
- log_output.should =~ /^INSERT INTO `articles` \(`id`\) VALUES \(.{1,2}\)$/i
93
+ regexp = if @mysql
94
+ /^INSERT INTO `articles` \(`id`\) VALUES \(.{1,2}\)$/i
95
+ elsif @sql_server
96
+ /^SET IDENTITY_INSERT \"articles\" ON INSERT INTO "articles" \("id"\) VALUES \(.{1,2}\) SET IDENTITY_INSERT \"articles\" OFF $/i
92
97
  else
93
- log_output.should =~ /^INSERT INTO "articles" \("id"\) VALUES \(.{1,2}\)$/i
98
+ /^INSERT INTO "articles" \("id"\) VALUES \(.{1,2}\)$/i
94
99
  end
100
+
101
+ log_output.first.should =~ regexp
102
+ end
103
+ end
104
+ end
105
+
106
+ describe '#select' do
107
+ before :all do
108
+ class ::Article
109
+ include DataMapper::Resource
110
+
111
+ property :name, String, :key => true
112
+ property :author, String, :required => true
113
+
114
+ auto_migrate!
115
+ end
116
+
117
+ @article_model = Article
118
+
119
+ @article_model.create(:name => 'Learning DataMapper', :author => 'Dan Kubb')
120
+ end
121
+
122
+ describe 'when one field specified in SELECT statement' do
123
+ before :all do
124
+ @return = @adapter.select('SELECT name FROM articles')
125
+ end
126
+
127
+ it 'should return an Array' do
128
+ @return.should be_kind_of(Array)
129
+ end
130
+
131
+ it 'should have a single result' do
132
+ @return.size.should == 1
133
+ end
134
+
135
+ it 'should return an Array of values' do
136
+ @return.should == [ 'Learning DataMapper' ]
137
+ end
138
+ end
139
+
140
+ describe 'when more than one field specified in SELECT statement' do
141
+ before :all do
142
+ @return = @adapter.select('SELECT name, author FROM articles')
143
+ end
144
+
145
+ it 'should return an Array' do
146
+ @return.should be_kind_of(Array)
147
+ end
148
+
149
+ it 'should have a single result' do
150
+ @return.size.should == 1
151
+ end
152
+
153
+ it 'should return an Array of Struct objects' do
154
+ @return.first.should be_kind_of(Struct)
155
+ end
156
+
157
+ it 'should return expected values' do
158
+ @return.first.values.should == [ 'Learning DataMapper', 'Dan Kubb' ]
95
159
  end
96
160
  end
97
161
  end
98
162
 
99
163
  describe '#execute' do
100
- it 'should allow queries without return results'
164
+ before :all do
165
+ class ::Article
166
+ include DataMapper::Resource
167
+
168
+ property :name, String, :key => true
169
+ property :author, String, :required => true
170
+
171
+ auto_migrate!
172
+ end
173
+
174
+ @article_model = Article
175
+ end
176
+
177
+ before :all do
178
+ @result = @adapter.execute('INSERT INTO articles (name, author) VALUES(?, ?)', 'Learning DataMapper', 'Dan Kubb')
179
+ end
180
+
181
+ it 'should return a DataObjects::Result' do
182
+ @result.should be_kind_of(DataObjects::Result)
183
+ end
184
+
185
+ it 'should affect 1 row' do
186
+ @result.affected_rows.should == 1
187
+ end
188
+
189
+ it 'should not have an insert_id' do
190
+ pending_if 'Inconsistent insert_id results', !(@postgres || @oracle || (@jruby && @mysql)) do
191
+ @result.insert_id.should be_nil
192
+ end
193
+ end
101
194
  end
102
195
 
103
- describe '#query' do
104
- it 'should allow queries with return results'
196
+ describe '#read' do
197
+ before :all do
198
+ class ::Article
199
+ include DataMapper::Resource
200
+
201
+ property :name, String, :key => true
202
+
203
+ belongs_to :parent, self, :required => false
204
+ has n, :children, self, :inverse => :parent
205
+
206
+ auto_migrate!
207
+ end
208
+
209
+ @article_model = Article
210
+ end
211
+
212
+ describe 'with a raw query' do
213
+ before :all do
214
+ @article_model.create(:name => 'Test').should be_saved
215
+
216
+ @query = DataMapper::Query.new(@repository, @article_model, :conditions => [ 'name IS NOT NULL' ])
217
+
218
+ @return = @adapter.read(@query)
219
+ end
220
+
221
+ it 'should return an Array of Hashes' do
222
+ @return.should be_kind_of(Array)
223
+ @return.all? { |entry| entry.should be_kind_of(Hash) }
224
+ end
225
+
226
+ it 'should return expected values' do
227
+ @return.should == [ { @article_model.properties[:name] => 'Test', @article_model.properties[:parent_name] => nil } ]
228
+ end
229
+ end
230
+
231
+ describe 'with a raw query with a bind value mismatch' do
232
+ before :all do
233
+ @article_model.create(:name => 'Test').should be_saved
234
+
235
+ @query = DataMapper::Query.new(@repository, @article_model, :conditions => [ 'name IS NOT NULL', nil ])
236
+ end
237
+
238
+ it 'should raise an error' do
239
+ lambda {
240
+ @adapter.read(@query)
241
+ }.should raise_error(ArgumentError, 'Binding mismatch: 1 for 0')
242
+ end
243
+ end
244
+
245
+ describe 'with a Collection bind value' do
246
+ describe 'with an inclusion comparison' do
247
+ before :all do
248
+ 5.times do |index|
249
+ @article_model.create(:name => "Test #{index}", :parent => @article_model.last).should be_saved
250
+ end
251
+
252
+ @parents = @article_model.all
253
+ @query = DataMapper::Query.new(@repository, @article_model, :parent => @parents)
254
+
255
+ @expected = @article_model.all[1, 4].map { |article| article.attributes(:property) }
256
+ end
257
+
258
+ describe 'that is not loaded' do
259
+ before :all do
260
+ reset_log
261
+ @return = @adapter.read(@query)
262
+ end
263
+
264
+ it 'should return an Array of Hashes' do
265
+ @return.should be_kind_of(Array)
266
+ @return.all? { |entry| entry.should be_kind_of(Hash) }
267
+ end
268
+
269
+ it 'should return expected values' do
270
+ @return.should == @expected
271
+ end
272
+
273
+ it 'should execute one subquery' do
274
+ pending_if @mysql do
275
+ log_output.size.should == 1
276
+ end
277
+ end
278
+ end
279
+
280
+ describe 'that is loaded' do
281
+ before :all do
282
+ @parents.to_a # lazy load the collection
283
+ end
284
+
285
+ before :all do
286
+ reset_log
287
+ @return = @adapter.read(@query)
288
+ end
289
+
290
+ it 'should return an Array of Hashes' do
291
+ @return.should be_kind_of(Array)
292
+ @return.all? { |entry| entry.should be_kind_of(Hash) }
293
+ end
294
+
295
+ it 'should return expected values' do
296
+ @return.should == @expected
297
+ end
298
+
299
+ it 'should execute one query' do
300
+ log_output.size.should == 1
301
+ end
302
+ end
303
+ end
304
+
305
+ describe 'with an negated inclusion comparison' do
306
+ before :all do
307
+ 5.times do |index|
308
+ @article_model.create(:name => "Test #{index}", :parent => @article_model.last).should be_saved
309
+ end
310
+
311
+ @parents = @article_model.all
312
+ @query = DataMapper::Query.new(@repository, @article_model, :parent.not => @parents)
313
+
314
+ @expected = []
315
+ end
316
+
317
+ describe 'that is not loaded' do
318
+ before :all do
319
+ reset_log
320
+ @return = @adapter.read(@query)
321
+ end
322
+
323
+ it 'should return an Array of Hashes' do
324
+ @return.should be_kind_of(Array)
325
+ @return.all? { |entry| entry.should be_kind_of(Hash) }
326
+ end
327
+
328
+ it 'should return expected values' do
329
+ @return.should == @expected
330
+ end
331
+
332
+ it 'should execute one subquery' do
333
+ pending_if @mysql do
334
+ log_output.size.should == 1
335
+ end
336
+ end
337
+ end
338
+
339
+ describe 'that is loaded' do
340
+ before :all do
341
+ @parents.to_a # lazy load the collection
342
+ end
343
+
344
+ before :all do
345
+ reset_log
346
+ @return = @adapter.read(@query)
347
+ end
348
+
349
+ it 'should return an Array of Hashes' do
350
+ @return.should be_kind_of(Array)
351
+ @return.all? { |entry| entry.should be_kind_of(Hash) }
352
+ end
353
+
354
+ it 'should return expected values' do
355
+ @return.should == @expected
356
+ end
357
+
358
+ it 'should execute one query' do
359
+ log_output.size.should == 1
360
+ end
361
+ end
362
+ end
363
+
364
+ end
105
365
  end
106
366
  end
@@ -1,7 +1,6 @@
1
1
  module DataMapper
2
2
  module Chainable
3
3
 
4
- # TODO: document
5
4
  # @api private
6
5
  def chainable(&block)
7
6
  mod = Module.new(&block)
@@ -9,7 +8,6 @@ module DataMapper
9
8
  mod
10
9
  end
11
10
 
12
- # TODO: document
13
11
  # @api private
14
12
  def extendable(&block)
15
13
  mod = Module.new(&block)
@@ -1,21 +1,45 @@
1
1
  module DataMapper
2
2
  module Equalizer
3
3
  def equalize(*methods)
4
+ define_eql_method(methods)
5
+ define_equivalent_method(methods)
6
+ define_hash_method(methods)
7
+ end
8
+
9
+ private
10
+
11
+ def define_eql_method(methods)
4
12
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
5
13
  def eql?(other)
6
14
  return true if equal?(other)
7
15
  instance_of?(other.class) &&
8
16
  #{methods.map { |method| "#{method}.eql?(other.#{method})" }.join(' && ')}
9
17
  end
18
+ RUBY
19
+ end
20
+
21
+ def define_equivalent_method(methods)
22
+ respond_to = []
23
+ equivalent = []
10
24
 
25
+ methods.each do |method|
26
+ respond_to << "other.respond_to?(#{method.inspect})"
27
+ equivalent << "#{method} == other.#{method}"
28
+ end
29
+
30
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
11
31
  def ==(other)
12
32
  return true if equal?(other)
13
- #{methods.map { |method| "other.respond_to?(#{method.inspect})" }.join(' && ')} &&
14
- #{methods.map { |method| "#{method} == other.#{method}" }.join(' && ')}
33
+ #{respond_to.join(' && ')} &&
34
+ #{equivalent.join(' && ')}
15
35
  end
36
+ RUBY
37
+ end
16
38
 
39
+ def define_hash_method(methods)
40
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
17
41
  def hash
18
- [ #{methods.join(', ')} ].hash
42
+ #{methods.map { |method| "#{method}.hash" }.join(' ^ ')}
19
43
  end
20
44
  RUBY
21
45
  end
@@ -4,6 +4,29 @@ module DataMapper
4
4
  class Transaction
5
5
  extend Chainable
6
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
+
7
30
  # Create a new Transaction
8
31
  #
9
32
  # @see Transaction#link
@@ -14,7 +37,7 @@ module DataMapper
14
37
  # @api public
15
38
  def initialize(*things)
16
39
  @transaction_primitives = {}
17
- @state = :none
40
+ self.state = :none
18
41
  @adapters = {}
19
42
  link(*things)
20
43
  if block_given?
@@ -44,8 +67,8 @@ module DataMapper
44
67
  #
45
68
  # @api private
46
69
  def link(*things)
47
- unless @state == :none
48
- raise "Illegal state for link: #{@state}"
70
+ unless none?
71
+ raise "Illegal state for link: #{state}"
49
72
  end
50
73
 
51
74
  things.each do |thing|
@@ -78,13 +101,13 @@ module DataMapper
78
101
  #
79
102
  # @api private
80
103
  def begin
81
- unless @state == :none
82
- raise "Illegal state for begin: #{@state}"
104
+ unless none?
105
+ raise "Illegal state for begin: #{state}"
83
106
  end
84
107
 
85
108
  each_adapter(:connect_adapter, [:log_fatal_transaction_breakage])
86
109
  each_adapter(:begin_adapter, [:rollback_and_close_adapter_if_begin, :close_adapter_if_none])
87
- @state = :begin
110
+ self.state = :begin
88
111
  end
89
112
 
90
113
  # Commit the transaction
@@ -99,30 +122,33 @@ module DataMapper
99
122
  # @api private
100
123
  def commit
101
124
  if block_given?
102
- unless @state == :none
103
- raise "Illegal state for commit with block: #{@state}"
125
+ unless none?
126
+ raise "Illegal state for commit with block: #{state}"
104
127
  end
105
128
 
106
129
  begin
107
130
  self.begin
108
131
  rval = within { |*block_args| yield(*block_args) }
109
- if @state == :begin
110
- self.commit
111
- end
112
- return rval
113
132
  rescue Exception => exception
114
- if @state == :begin
115
- self.rollback
133
+ if begin?
134
+ rollback
116
135
  end
117
136
  raise exception
137
+ ensure
138
+ unless exception
139
+ if begin?
140
+ commit
141
+ end
142
+ return rval
143
+ end
118
144
  end
119
145
  else
120
- unless @state == :begin
121
- raise "Illegal state for commit without block: #{@state}"
146
+ unless begin?
147
+ raise "Illegal state for commit without block: #{state}"
122
148
  end
123
149
  each_adapter(:commit_adapter, [:log_fatal_transaction_breakage])
124
150
  each_adapter(:close_adapter, [:log_fatal_transaction_breakage])
125
- @state = :commit
151
+ self.state = :commit
126
152
  end
127
153
  end
128
154
 
@@ -132,12 +158,12 @@ module DataMapper
132
158
  #
133
159
  # @api private
134
160
  def rollback
135
- unless @state == :begin
136
- raise "Illegal state for rollback: #{@state}"
161
+ unless begin?
162
+ raise "Illegal state for rollback: #{state}"
137
163
  end
138
164
  each_adapter(:rollback_adapter_if_begin, [:rollback_and_close_adapter_if_begin, :close_adapter_if_none])
139
165
  each_adapter(:close_adapter_if_open, [:log_fatal_transaction_breakage])
140
- @state = :rollback
166
+ self.state = :rollback
141
167
  end
142
168
 
143
169
  # Execute a block within this Transaction.
@@ -155,52 +181,41 @@ module DataMapper
155
181
  raise 'No block provided'
156
182
  end
157
183
 
158
- unless @state == :begin
159
- raise "Illegal state for within: #{@state}"
184
+ unless begin?
185
+ raise "Illegal state for within: #{state}"
160
186
  end
161
187
 
162
- @adapters.each do |adapter, state|
188
+ adapters = @adapters
189
+
190
+ adapters.each_key do |adapter|
163
191
  adapter.push_transaction(self)
164
192
  end
165
193
 
166
194
  begin
167
195
  yield self
168
196
  ensure
169
- @adapters.each do |adapter, state|
197
+ adapters.each_key do |adapter|
170
198
  adapter.pop_transaction
171
199
  end
172
200
  end
173
201
  end
174
202
 
175
- # TODO: document
176
203
  # @api private
177
- def method_missing(meth, *args, &block)
178
- if args.size == 1 && args.first.kind_of?(Adapters::AbstractAdapter)
179
- if (match = meth.to_s.match(/^(.*)_if_(none|begin|rollback|commit)$/))
180
- if self.respond_to?(match[1], true)
181
- if state_for(args.first).to_s == match[2]
182
- self.send(match[1], args.first)
183
- end
184
- else
185
- super
186
- end
187
- elsif (match = meth.to_s.match(/^(.*)_unless_(none|begin|rollback|commit)$/))
188
- if self.respond_to?(match[1], true)
189
- unless state_for(args.first).to_s == match[2]
190
- self.send(match[1], args.first)
191
- end
192
- else
193
- super
194
- end
195
- else
196
- super
197
- end
198
- else
199
- super
200
- end
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
201
217
  end
202
218
 
203
- # TODO: document
204
219
  # @api private
205
220
  def primitive_for(adapter)
206
221
  unless @adapters.include?(adapter)
@@ -216,7 +231,6 @@ module DataMapper
216
231
 
217
232
  private
218
233
 
219
- # TODO: document
220
234
  # @api private
221
235
  def validate_primitive(primitive)
222
236
  [:close, :begin, :rollback, :commit].each do |meth|
@@ -228,18 +242,18 @@ module DataMapper
228
242
  primitive
229
243
  end
230
244
 
231
- # TODO: document
232
245
  # @api private
233
246
  def each_adapter(method, on_fail)
247
+ adapters = @adapters
234
248
  begin
235
- @adapters.each do |adapter, state|
236
- self.send(method, adapter)
249
+ adapters.each_key do |adapter|
250
+ send(method, adapter)
237
251
  end
238
252
  rescue Exception => exception
239
- @adapters.each do |adapter, state|
253
+ adapters.each_key do |adapter|
240
254
  on_fail.each do |fail_handler|
241
255
  begin
242
- self.send(fail_handler, adapter)
256
+ send(fail_handler, adapter)
243
257
  rescue Exception => inner_exception
244
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")}")
245
259
  end
@@ -249,7 +263,6 @@ module DataMapper
249
263
  end
250
264
  end
251
265
 
252
- # TODO: document
253
266
  # @api private
254
267
  def state_for(adapter)
255
268
  unless @adapters.include?(adapter)
@@ -259,15 +272,16 @@ module DataMapper
259
272
  @adapters[adapter]
260
273
  end
261
274
 
262
- # TODO: document
263
275
  # @api private
264
276
  def do_adapter(adapter, what, prerequisite)
265
277
  unless @transaction_primitives.include?(adapter)
266
278
  raise "No primitive for #{adapter}"
267
279
  end
268
280
 
269
- unless state_for(adapter) == prerequisite
270
- raise "Illegal state for #{what}: #{state_for(adapter)}"
281
+ state = state_for(adapter)
282
+
283
+ unless state == prerequisite
284
+ raise "Illegal state for #{what}: #{state}"
271
285
  end
272
286
 
273
287
  DataMapper.logger.debug("#{adapter.name}: #{what}")
@@ -275,13 +289,11 @@ module DataMapper
275
289
  @adapters[adapter] = what
276
290
  end
277
291
 
278
- # TODO: document
279
292
  # @api private
280
293
  def log_fatal_transaction_breakage(adapter)
281
294
  DataMapper.logger.fatal("#{self} experienced a totally broken transaction execution. Presenting member #{adapter.inspect}.")
282
295
  end
283
296
 
284
- # TODO: document
285
297
  # @api private
286
298
  def connect_adapter(adapter)
287
299
  if @transaction_primitives.key?(adapter)
@@ -291,7 +303,6 @@ module DataMapper
291
303
  @transaction_primitives[adapter] = validate_primitive(adapter.transaction_primitive)
292
304
  end
293
305
 
294
- # TODO: document
295
306
  # @api private
296
307
  def close_adapter_if_open(adapter)
297
308
  if @transaction_primitives.include?(adapter)
@@ -299,7 +310,6 @@ module DataMapper
299
310
  end
300
311
  end
301
312
 
302
- # TODO: document
303
313
  # @api private
304
314
  def close_adapter(adapter)
305
315
  unless @transaction_primitives.include?(adapter)
@@ -310,25 +320,21 @@ module DataMapper
310
320
  @transaction_primitives.delete(adapter)
311
321
  end
312
322
 
313
- # TODO: document
314
323
  # @api private
315
324
  def begin_adapter(adapter)
316
325
  do_adapter(adapter, :begin, :none)
317
326
  end
318
327
 
319
- # TODO: document
320
328
  # @api private
321
329
  def commit_adapter(adapter)
322
330
  do_adapter(adapter, :commit, :begin)
323
331
  end
324
332
 
325
- # TODO: document
326
333
  # @api private
327
334
  def rollback_adapter(adapter)
328
335
  do_adapter(adapter, :rollback, :begin)
329
336
  end
330
337
 
331
- # TODO: document
332
338
  # @api private
333
339
  def rollback_and_close_adapter(adapter)
334
340
  rollback_adapter(adapter)
@@ -338,7 +344,6 @@ module DataMapper
338
344
  module Adapter
339
345
  extend Chainable
340
346
 
341
- # TODO: document
342
347
  # @api private
343
348
  def self.included(base)
344
349
  [ :Repository, :Model, :Resource ].each do |name|
@@ -402,13 +407,11 @@ module DataMapper
402
407
  chainable do
403
408
  protected
404
409
 
405
- # TODO: document
406
410
  # @api semipublic
407
411
  def open_connection
408
412
  current_connection || super
409
413
  end
410
414
 
411
- # TODO: document
412
415
  # @api semipublic
413
416
  def close_connection(connection)
414
417
  unless current_connection == connection
@@ -419,7 +422,6 @@ module DataMapper
419
422
 
420
423
  private
421
424
 
422
- # TODO: document
423
425
  # @api private
424
426
  def transactions
425
427
  Thread.current[:dm_transactions] ||= []
@@ -439,7 +441,7 @@ module DataMapper
439
441
  end # module Adapter
440
442
 
441
443
  # alias the MySQL, PostgreSQL, Sqlite3 and Oracle adapters to use transactions
442
- MysqlAdapter = PostgresAdapter = Sqlite3Adapter = OracleAdapter = Adapter
444
+ MysqlAdapter = PostgresAdapter = Sqlite3Adapter = OracleAdapter = SqlserverAdapter = Adapter
443
445
 
444
446
  module Repository
445
447
 
@@ -456,7 +458,6 @@ module DataMapper
456
458
  end # module Repository
457
459
 
458
460
  module Model
459
- # TODO: document
460
461
  # @api private
461
462
  def self.included(mod)
462
463
  mod.descendants.each { |model| model.extend self }
@@ -493,7 +494,6 @@ module DataMapper
493
494
  module Adapters
494
495
  extendable do
495
496
 
496
- # TODO: document
497
497
  # @api private
498
498
  def const_added(const_name)
499
499
  if Transaction.const_defined?(const_name)