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.
- data/.autotest +29 -0
- data/.document +5 -0
- data/.gitignore +27 -0
- data/LICENSE +20 -0
- data/{README.txt → README.rdoc} +14 -3
- data/Rakefile +23 -22
- data/VERSION +1 -0
- data/dm-core.gemspec +201 -10
- data/lib/dm-core.rb +32 -23
- data/lib/dm-core/adapters.rb +0 -1
- data/lib/dm-core/adapters/data_objects_adapter.rb +230 -151
- data/lib/dm-core/adapters/mysql_adapter.rb +7 -8
- data/lib/dm-core/adapters/oracle_adapter.rb +39 -59
- data/lib/dm-core/adapters/postgres_adapter.rb +0 -1
- data/lib/dm-core/adapters/sqlite3_adapter.rb +5 -0
- data/lib/dm-core/adapters/sqlserver_adapter.rb +114 -0
- data/lib/dm-core/adapters/yaml_adapter.rb +0 -5
- data/lib/dm-core/associations/many_to_many.rb +118 -56
- data/lib/dm-core/associations/many_to_one.rb +48 -21
- data/lib/dm-core/associations/one_to_many.rb +8 -30
- data/lib/dm-core/associations/one_to_one.rb +1 -5
- data/lib/dm-core/associations/relationship.rb +89 -97
- data/lib/dm-core/collection.rb +299 -184
- data/lib/dm-core/core_ext/enumerable.rb +28 -0
- data/lib/dm-core/core_ext/kernel.rb +0 -2
- data/lib/dm-core/migrations.rb +314 -170
- data/lib/dm-core/model.rb +97 -66
- data/lib/dm-core/model/descendant_set.rb +1 -1
- data/lib/dm-core/model/hook.rb +0 -3
- data/lib/dm-core/model/property.rb +7 -10
- data/lib/dm-core/model/relationship.rb +79 -26
- data/lib/dm-core/model/scope.rb +3 -4
- data/lib/dm-core/property.rb +152 -90
- data/lib/dm-core/property_set.rb +18 -37
- data/lib/dm-core/query.rb +452 -153
- data/lib/dm-core/query/conditions/comparison.rb +266 -173
- data/lib/dm-core/query/conditions/operation.rb +499 -57
- data/lib/dm-core/query/direction.rb +0 -3
- data/lib/dm-core/query/operator.rb +0 -4
- data/lib/dm-core/query/path.rb +10 -12
- data/lib/dm-core/query/sort.rb +4 -10
- data/lib/dm-core/repository.rb +10 -6
- data/lib/dm-core/resource.rb +343 -148
- data/lib/dm-core/spec/adapter_shared_spec.rb +17 -1
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +277 -17
- data/lib/dm-core/support/chainable.rb +0 -2
- data/lib/dm-core/support/equalizer.rb +27 -3
- data/lib/dm-core/transaction.rb +75 -75
- data/lib/dm-core/type.rb +19 -5
- data/lib/dm-core/types/discriminator.rb +4 -4
- data/lib/dm-core/types/object.rb +2 -7
- data/lib/dm-core/types/paranoid_boolean.rb +8 -2
- data/lib/dm-core/types/paranoid_datetime.rb +8 -2
- data/lib/dm-core/version.rb +1 -1
- data/script/performance.rb +7 -7
- data/script/profile.rb +6 -6
- data/spec/lib/collection_helpers.rb +2 -2
- data/spec/lib/pending_helpers.rb +22 -3
- data/spec/lib/rspec_immediate_feedback_formatter.rb +1 -0
- data/spec/public/associations/many_to_many_spec.rb +6 -4
- data/spec/public/associations/many_to_one_spec.rb +10 -1
- data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +39 -0
- data/spec/public/associations/one_to_many_spec.rb +4 -3
- data/spec/public/associations/one_to_one_spec.rb +19 -1
- data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +45 -0
- data/spec/public/collection_spec.rb +4 -3
- data/spec/public/migrations_spec.rb +144 -0
- data/spec/public/model/relationship_spec.rb +115 -55
- data/spec/public/model_spec.rb +13 -13
- data/spec/public/property/object_spec.rb +106 -0
- data/spec/public/property_spec.rb +18 -14
- data/spec/public/resource_spec.rb +10 -1
- data/spec/public/sel_spec.rb +16 -49
- data/spec/public/setup_spec.rb +1 -1
- data/spec/public/shared/association_collection_shared_spec.rb +6 -14
- data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
- data/spec/public/shared/collection_shared_spec.rb +214 -217
- data/spec/public/shared/finder_shared_spec.rb +259 -365
- data/spec/public/shared/resource_shared_spec.rb +524 -248
- data/spec/public/transaction_spec.rb +27 -3
- data/spec/public/types/discriminator_spec.rb +1 -1
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +17 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +3 -20
- data/spec/semipublic/associations_spec.rb +2 -2
- data/spec/semipublic/collection_spec.rb +0 -32
- data/spec/semipublic/model_spec.rb +96 -0
- data/spec/semipublic/property_spec.rb +3 -3
- data/spec/semipublic/query/conditions/comparison_spec.rb +1719 -0
- data/spec/semipublic/query/conditions/operation_spec.rb +1292 -0
- data/spec/semipublic/query_spec.rb +1285 -144
- data/spec/semipublic/resource_spec.rb +0 -24
- data/spec/semipublic/shared/resource_shared_spec.rb +103 -38
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +15 -6
- data/tasks/ci.rake +1 -0
- data/tasks/metrics.rake +37 -0
- data/tasks/spec.rake +41 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +99 -29
- data/CONTRIBUTING +0 -51
- data/FAQ +0 -93
- data/History.txt +0 -27
- data/MIT-LICENSE +0 -22
- data/Manifest.txt +0 -121
- data/QUICKLINKS +0 -11
- data/SPECS +0 -35
- data/TODO +0 -1
- data/spec/semipublic/query/conditions_spec.rb +0 -528
- data/tasks/ci.rb +0 -24
- data/tasks/dm.rb +0 -58
- data/tasks/doc.rb +0 -17
- data/tasks/gemspec.rb +0 -23
- data/tasks/hoe.rb +0 -45
- 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
|
-
|
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
|
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
|
-
|
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
|
-
|
91
|
-
|
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
|
-
|
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
|
-
|
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 '#
|
104
|
-
|
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
|
-
#{
|
14
|
-
#{
|
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
|
-
|
42
|
+
#{methods.map { |method| "#{method}.hash" }.join(' ^ ')}
|
19
43
|
end
|
20
44
|
RUBY
|
21
45
|
end
|
data/lib/dm-core/transaction.rb
CHANGED
@@ -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
|
-
|
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
|
48
|
-
raise "Illegal state for link: #{
|
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
|
82
|
-
raise "Illegal state for begin: #{
|
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
|
-
|
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
|
103
|
-
raise "Illegal state for commit with block: #{
|
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
|
115
|
-
|
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
|
121
|
-
raise "Illegal state for commit without block: #{
|
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
|
-
|
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
|
136
|
-
raise "Illegal state for rollback: #{
|
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
|
-
|
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
|
159
|
-
raise "Illegal state for within: #{
|
184
|
+
unless begin?
|
185
|
+
raise "Illegal state for within: #{state}"
|
160
186
|
end
|
161
187
|
|
162
|
-
|
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
|
-
|
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(
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
-
|
236
|
-
|
249
|
+
adapters.each_key do |adapter|
|
250
|
+
send(method, adapter)
|
237
251
|
end
|
238
252
|
rescue Exception => exception
|
239
|
-
|
253
|
+
adapters.each_key do |adapter|
|
240
254
|
on_fail.each do |fail_handler|
|
241
255
|
begin
|
242
|
-
|
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
|
-
|
270
|
-
|
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)
|