dm-core 0.10.2 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (183) hide show
  1. data/.gitignore +10 -1
  2. data/Gemfile +143 -0
  3. data/Rakefile +9 -5
  4. data/VERSION +1 -1
  5. data/dm-core.gemspec +160 -57
  6. data/lib/dm-core.rb +131 -56
  7. data/lib/dm-core/adapters.rb +98 -14
  8. data/lib/dm-core/adapters/abstract_adapter.rb +24 -4
  9. data/lib/dm-core/adapters/in_memory_adapter.rb +7 -2
  10. data/lib/dm-core/associations/many_to_many.rb +19 -30
  11. data/lib/dm-core/associations/many_to_one.rb +58 -42
  12. data/lib/dm-core/associations/one_to_many.rb +33 -23
  13. data/lib/dm-core/associations/one_to_one.rb +27 -11
  14. data/lib/dm-core/associations/relationship.rb +4 -4
  15. data/lib/dm-core/collection.rb +23 -16
  16. data/lib/dm-core/core_ext/array.rb +36 -0
  17. data/lib/dm-core/core_ext/hash.rb +30 -0
  18. data/lib/dm-core/core_ext/module.rb +46 -0
  19. data/lib/dm-core/core_ext/object.rb +31 -0
  20. data/lib/dm-core/core_ext/pathname.rb +20 -0
  21. data/lib/dm-core/core_ext/string.rb +22 -0
  22. data/lib/dm-core/core_ext/try_dup.rb +44 -0
  23. data/lib/dm-core/model.rb +88 -27
  24. data/lib/dm-core/model/hook.rb +75 -18
  25. data/lib/dm-core/model/property.rb +50 -9
  26. data/lib/dm-core/model/relationship.rb +31 -31
  27. data/lib/dm-core/model/scope.rb +3 -3
  28. data/lib/dm-core/property.rb +196 -516
  29. data/lib/dm-core/property/binary.rb +7 -0
  30. data/lib/dm-core/property/boolean.rb +35 -0
  31. data/lib/dm-core/property/class.rb +24 -0
  32. data/lib/dm-core/property/date.rb +47 -0
  33. data/lib/dm-core/property/date_time.rb +48 -0
  34. data/lib/dm-core/property/decimal.rb +43 -0
  35. data/lib/dm-core/property/discriminator.rb +48 -0
  36. data/lib/dm-core/property/float.rb +24 -0
  37. data/lib/dm-core/property/integer.rb +32 -0
  38. data/lib/dm-core/property/numeric.rb +43 -0
  39. data/lib/dm-core/property/object.rb +32 -0
  40. data/lib/dm-core/property/serial.rb +8 -0
  41. data/lib/dm-core/property/string.rb +49 -0
  42. data/lib/dm-core/property/text.rb +12 -0
  43. data/lib/dm-core/property/time.rb +48 -0
  44. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  45. data/lib/dm-core/property/typecast/time.rb +28 -0
  46. data/lib/dm-core/property_set.rb +10 -4
  47. data/lib/dm-core/query.rb +14 -37
  48. data/lib/dm-core/query/conditions/comparison.rb +8 -6
  49. data/lib/dm-core/query/conditions/operation.rb +33 -2
  50. data/lib/dm-core/query/operator.rb +2 -5
  51. data/lib/dm-core/query/path.rb +4 -6
  52. data/lib/dm-core/repository.rb +21 -6
  53. data/lib/dm-core/resource.rb +316 -133
  54. data/lib/dm-core/resource/state.rb +79 -0
  55. data/lib/dm-core/resource/state/clean.rb +40 -0
  56. data/lib/dm-core/resource/state/deleted.rb +30 -0
  57. data/lib/dm-core/resource/state/dirty.rb +86 -0
  58. data/lib/dm-core/resource/state/immutable.rb +34 -0
  59. data/lib/dm-core/resource/state/persisted.rb +29 -0
  60. data/lib/dm-core/resource/state/transient.rb +70 -0
  61. data/lib/dm-core/spec/lib/adapter_helpers.rb +52 -0
  62. data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
  63. data/{spec → lib/dm-core/spec}/lib/counter_adapter.rb +5 -1
  64. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  65. data/lib/dm-core/spec/lib/spec_helper.rb +68 -0
  66. data/lib/dm-core/spec/setup.rb +165 -0
  67. data/lib/dm-core/spec/{adapter_shared_spec.rb → shared/adapter_spec.rb} +21 -7
  68. data/{spec/public/shared/resource_shared_spec.rb → lib/dm-core/spec/shared/resource_spec.rb} +120 -83
  69. data/{spec/public/shared/sel_shared_spec.rb → lib/dm-core/spec/shared/sel_spec.rb} +5 -6
  70. data/lib/dm-core/support/assertions.rb +8 -0
  71. data/lib/dm-core/support/equalizer.rb +1 -0
  72. data/lib/dm-core/support/hook.rb +420 -0
  73. data/lib/dm-core/support/lazy_array.rb +453 -0
  74. data/lib/dm-core/support/local_object_space.rb +12 -0
  75. data/lib/dm-core/support/logger.rb +193 -6
  76. data/lib/dm-core/support/naming_conventions.rb +8 -8
  77. data/lib/dm-core/support/subject.rb +33 -0
  78. data/lib/dm-core/type.rb +4 -0
  79. data/lib/dm-core/types/boolean.rb +2 -0
  80. data/lib/dm-core/types/decimal.rb +9 -0
  81. data/lib/dm-core/types/discriminator.rb +2 -0
  82. data/lib/dm-core/types/object.rb +3 -0
  83. data/lib/dm-core/types/serial.rb +2 -0
  84. data/lib/dm-core/types/text.rb +2 -0
  85. data/lib/dm-core/version.rb +1 -1
  86. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +67 -0
  87. data/spec/public/model/hook_spec.rb +209 -0
  88. data/spec/public/model/property_spec.rb +35 -0
  89. data/spec/public/model/relationship_spec.rb +33 -20
  90. data/spec/public/model_spec.rb +142 -10
  91. data/spec/public/property/binary_spec.rb +14 -0
  92. data/spec/public/property/boolean_spec.rb +14 -0
  93. data/spec/public/property/class_spec.rb +20 -0
  94. data/spec/public/property/date_spec.rb +14 -0
  95. data/spec/public/property/date_time_spec.rb +14 -0
  96. data/spec/public/property/decimal_spec.rb +14 -0
  97. data/spec/public/{types → property}/discriminator_spec.rb +2 -12
  98. data/spec/public/property/float_spec.rb +14 -0
  99. data/spec/public/property/integer_spec.rb +14 -0
  100. data/spec/public/property/object_spec.rb +9 -17
  101. data/spec/public/property/serial_spec.rb +14 -0
  102. data/spec/public/property/string_spec.rb +14 -0
  103. data/spec/public/property/text_spec.rb +52 -0
  104. data/spec/public/property/time_spec.rb +14 -0
  105. data/spec/public/property_spec.rb +28 -87
  106. data/spec/public/resource_spec.rb +101 -0
  107. data/spec/public/sel_spec.rb +5 -15
  108. data/spec/public/shared/collection_shared_spec.rb +16 -30
  109. data/spec/public/shared/finder_shared_spec.rb +2 -4
  110. data/spec/public/shared/property_shared_spec.rb +176 -0
  111. data/spec/semipublic/adapters/abstract_adapter_spec.rb +1 -1
  112. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +2 -2
  113. data/spec/semipublic/associations/many_to_many_spec.rb +89 -0
  114. data/spec/semipublic/associations/many_to_one_spec.rb +24 -1
  115. data/spec/semipublic/associations/one_to_many_spec.rb +51 -0
  116. data/spec/semipublic/associations/one_to_one_spec.rb +49 -0
  117. data/spec/semipublic/associations/relationship_spec.rb +3 -3
  118. data/spec/semipublic/associations_spec.rb +1 -1
  119. data/spec/semipublic/property/binary_spec.rb +13 -0
  120. data/spec/semipublic/property/boolean_spec.rb +65 -0
  121. data/spec/semipublic/property/class_spec.rb +33 -0
  122. data/spec/semipublic/property/date_spec.rb +43 -0
  123. data/spec/semipublic/property/date_time_spec.rb +46 -0
  124. data/spec/semipublic/property/decimal_spec.rb +82 -0
  125. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  126. data/spec/semipublic/property/float_spec.rb +82 -0
  127. data/spec/semipublic/property/integer_spec.rb +82 -0
  128. data/spec/semipublic/property/serial_spec.rb +13 -0
  129. data/spec/semipublic/property/string_spec.rb +13 -0
  130. data/spec/semipublic/property/text_spec.rb +31 -0
  131. data/spec/semipublic/property/time_spec.rb +50 -0
  132. data/spec/semipublic/property_spec.rb +2 -532
  133. data/spec/semipublic/query/conditions/comparison_spec.rb +171 -169
  134. data/spec/semipublic/query/conditions/operation_spec.rb +53 -51
  135. data/spec/semipublic/query/path_spec.rb +17 -17
  136. data/spec/semipublic/query_spec.rb +47 -78
  137. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  138. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  139. data/spec/semipublic/resource/state/dirty_spec.rb +133 -0
  140. data/spec/semipublic/resource/state/immutable_spec.rb +99 -0
  141. data/spec/semipublic/resource/state/transient_spec.rb +128 -0
  142. data/spec/semipublic/resource/state_spec.rb +226 -0
  143. data/spec/semipublic/shared/property_shared_spec.rb +143 -0
  144. data/spec/semipublic/shared/resource_shared_spec.rb +16 -15
  145. data/spec/semipublic/shared/resource_state_shared_spec.rb +78 -0
  146. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  147. data/spec/spec_helper.rb +21 -97
  148. data/spec/support/types/huge_integer.rb +17 -0
  149. data/spec/unit/array_spec.rb +48 -0
  150. data/spec/unit/hash_spec.rb +35 -0
  151. data/spec/unit/hook_spec.rb +1234 -0
  152. data/spec/unit/lazy_array_spec.rb +1959 -0
  153. data/spec/unit/module_spec.rb +70 -0
  154. data/spec/unit/object_spec.rb +37 -0
  155. data/spec/unit/try_dup_spec.rb +45 -0
  156. data/tasks/local_gemfile.rake +18 -0
  157. data/tasks/spec.rake +0 -3
  158. metadata +197 -71
  159. data/deps.rip +0 -2
  160. data/lib/dm-core/adapters/data_objects_adapter.rb +0 -712
  161. data/lib/dm-core/adapters/mysql_adapter.rb +0 -42
  162. data/lib/dm-core/adapters/oracle_adapter.rb +0 -229
  163. data/lib/dm-core/adapters/postgres_adapter.rb +0 -22
  164. data/lib/dm-core/adapters/sqlite3_adapter.rb +0 -17
  165. data/lib/dm-core/adapters/sqlserver_adapter.rb +0 -114
  166. data/lib/dm-core/adapters/yaml_adapter.rb +0 -111
  167. data/lib/dm-core/core_ext/enumerable.rb +0 -28
  168. data/lib/dm-core/migrations.rb +0 -1427
  169. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +0 -366
  170. data/lib/dm-core/transaction.rb +0 -508
  171. data/lib/dm-core/types/paranoid_boolean.rb +0 -42
  172. data/lib/dm-core/types/paranoid_datetime.rb +0 -41
  173. data/spec/lib/adapter_helpers.rb +0 -105
  174. data/spec/lib/collection_helpers.rb +0 -18
  175. data/spec/lib/pending_helpers.rb +0 -46
  176. data/spec/public/migrations_spec.rb +0 -503
  177. data/spec/public/transaction_spec.rb +0 -153
  178. data/spec/semipublic/adapters/mysql_adapter_spec.rb +0 -17
  179. data/spec/semipublic/adapters/oracle_adapter_spec.rb +0 -194
  180. data/spec/semipublic/adapters/postgres_adapter_spec.rb +0 -17
  181. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +0 -17
  182. data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +0 -17
  183. data/spec/semipublic/adapters/yaml_adapter_spec.rb +0 -12
@@ -1,366 +0,0 @@
1
- share_examples_for 'A DataObjects Adapter' do
2
- before :all do
3
- raise '+@adapter+ should be defined in before block' unless instance_variable_get('@adapter')
4
-
5
- @log = StringIO.new
6
-
7
- @original_logger = DataMapper.logger
8
- DataMapper.logger = DataMapper::Logger.new(@log, :debug)
9
-
10
- # set up the adapter after switching the logger so queries can be captured
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)
19
- end
20
-
21
- after :all do
22
- DataMapper.logger = @original_logger
23
- end
24
-
25
- def reset_log
26
- @log.truncate(0)
27
- @log.rewind
28
- end
29
-
30
- def log_output
31
- @log.rewind
32
- @log.read.chomp.gsub(/^\s+~ \(\d+\.?\d*\)\s+/, '').split("\n")
33
- end
34
-
35
- def supports_default_values?
36
- @adapter.send(:supports_default_values?)
37
- end
38
-
39
- def supports_returning?
40
- @adapter.send(:supports_returning?)
41
- end
42
-
43
- describe '#create' do
44
- describe 'serial properties' do
45
- before :all do
46
- class ::Article
47
- include DataMapper::Resource
48
-
49
- property :id, Serial
50
-
51
- auto_migrate!
52
- end
53
-
54
- reset_log
55
-
56
- Article.create
57
- end
58
-
59
- it 'should not send NULL values' do
60
- statement = if @mysql
61
- /\AINSERT INTO `articles` \(\) VALUES \(\)\z/
62
- elsif @oracle
63
- /\AINSERT INTO "ARTICLES" \("ID"\) VALUES \(DEFAULT\) RETURNING "ID"/
64
- elsif supports_default_values? && supports_returning?
65
- /\AINSERT INTO "articles" DEFAULT VALUES RETURNING \"id\"\z/
66
- elsif supports_default_values?
67
- /\AINSERT INTO "articles" DEFAULT VALUES\z/
68
- else
69
- /\AINSERT INTO "articles" \(\) VALUES \(\)\z/
70
- end
71
-
72
- log_output.first.should =~ statement
73
- end
74
- end
75
-
76
- describe 'properties without a default' do
77
- before :all do
78
- class ::Article
79
- include DataMapper::Resource
80
-
81
- property :id, Serial
82
- property :title, String
83
-
84
- auto_migrate!
85
- end
86
-
87
- reset_log
88
-
89
- Article.create(:id => 1)
90
- end
91
-
92
- it 'should not send NULL values' do
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
97
- else
98
- /^INSERT INTO "articles" \("id"\) VALUES \(.{1,2}\)$/i
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' ]
159
- end
160
- end
161
- end
162
-
163
- describe '#execute' do
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
194
- end
195
-
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
365
- end
366
- end
@@ -1,508 +0,0 @@
1
- # TODO: move to dm-more/dm-transaction
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 Adapter
345
- extend Chainable
346
-
347
- # @api private
348
- def self.included(base)
349
- [ :Repository, :Model, :Resource ].each do |name|
350
- DataMapper.const_get(name).send(:include, Transaction.const_get(name))
351
- end
352
- end
353
-
354
- # Produces a fresh transaction primitive for this Adapter
355
- #
356
- # Used by Transaction to perform its various tasks.
357
- #
358
- # @return [Object]
359
- # a new Object that responds to :close, :begin, :commit,
360
- # and :rollback,
361
- #
362
- # @api private
363
- def transaction_primitive
364
- DataObjects::Transaction.create_for_uri(normalized_uri)
365
- end
366
-
367
- # Pushes the given Transaction onto the per thread Transaction stack so
368
- # that everything done by this Adapter is done within the context of said
369
- # Transaction.
370
- #
371
- # @param [Transaction] transaction
372
- # a Transaction to be the 'current' transaction until popped.
373
- #
374
- # @return [Array(Transaction)]
375
- # the stack of active transactions for the current thread
376
- #
377
- # @api private
378
- def push_transaction(transaction)
379
- transactions << transaction
380
- end
381
-
382
- # Pop the 'current' Transaction from the per thread Transaction stack so
383
- # that everything done by this Adapter is no longer necessarily within the
384
- # context of said Transaction.
385
- #
386
- # @return [Transaction]
387
- # the former 'current' transaction.
388
- #
389
- # @api private
390
- def pop_transaction
391
- transactions.pop
392
- end
393
-
394
- # Retrieve the current transaction for this Adapter.
395
- #
396
- # Everything done by this Adapter is done within the context of this
397
- # Transaction.
398
- #
399
- # @return [Transaction]
400
- # the 'current' transaction for this Adapter.
401
- #
402
- # @api private
403
- def current_transaction
404
- transactions.last
405
- end
406
-
407
- chainable do
408
- protected
409
-
410
- # @api semipublic
411
- def open_connection
412
- current_connection || super
413
- end
414
-
415
- # @api semipublic
416
- def close_connection(connection)
417
- unless current_connection == connection
418
- super
419
- end
420
- end
421
- end
422
-
423
- private
424
-
425
- # @api private
426
- def transactions
427
- Thread.current[:dm_transactions] ||= []
428
- end
429
-
430
- # Retrieve the current connection for this Adapter.
431
- #
432
- # @return [Transaction]
433
- # the 'current' connection for this Adapter.
434
- #
435
- # @api private
436
- def current_connection
437
- if transaction = current_transaction
438
- transaction.primitive_for(self).connection
439
- end
440
- end
441
- end # module Adapter
442
-
443
- # alias the MySQL, PostgreSQL, Sqlite3 and Oracle adapters to use transactions
444
- MysqlAdapter = PostgresAdapter = Sqlite3Adapter = OracleAdapter = SqlserverAdapter = Adapter
445
-
446
- module Repository
447
-
448
- # Produce a new Transaction for this Repository
449
- #
450
- # @return [Adapters::Transaction]
451
- # a new Transaction (in state :none) that can be used
452
- # to execute code #with_transaction
453
- #
454
- # @api public
455
- def transaction
456
- Transaction.new(self)
457
- end
458
- end # module Repository
459
-
460
- module Model
461
- # @api private
462
- def self.included(mod)
463
- mod.descendants.each { |model| model.extend self }
464
- end
465
-
466
- # Produce a new Transaction for this Resource class
467
- #
468
- # @return <Adapters::Transaction
469
- # a new Adapters::Transaction with all Repositories
470
- # of the class of this Resource added.
471
- #
472
- # @api public
473
- def transaction
474
- transaction = Transaction.new(self)
475
- transaction.commit { |block_args| yield(*block_args) }
476
- end
477
- end # module Model
478
-
479
- module Resource
480
-
481
- # Produce a new Transaction for the class of this Resource
482
- #
483
- # @return [Adapters::Transaction]
484
- # a new Adapters::Transaction for the Repository
485
- # of the class of this Resource added.
486
- #
487
- # @api public
488
- def transaction
489
- model.transaction { |*block_args| yield(*block_args) }
490
- end
491
- end # module Resource
492
- end # class Transaction
493
-
494
- module Adapters
495
- extendable do
496
-
497
- # @api private
498
- def const_added(const_name)
499
- if Transaction.const_defined?(const_name)
500
- adapter = const_get(const_name)
501
- adapter.send(:include, Transaction.const_get(const_name))
502
- end
503
-
504
- super
505
- end
506
- end
507
- end # module Adapters
508
- end # module DataMapper