dm-core 0.10.2 → 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.
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