dm-core 0.10.1 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/.autotest +29 -0
  2. data/.document +5 -0
  3. data/.gitignore +27 -0
  4. data/LICENSE +20 -0
  5. data/{README.txt → README.rdoc} +14 -3
  6. data/Rakefile +23 -22
  7. data/VERSION +1 -0
  8. data/dm-core.gemspec +201 -10
  9. data/lib/dm-core.rb +32 -23
  10. data/lib/dm-core/adapters.rb +0 -1
  11. data/lib/dm-core/adapters/data_objects_adapter.rb +230 -151
  12. data/lib/dm-core/adapters/mysql_adapter.rb +7 -8
  13. data/lib/dm-core/adapters/oracle_adapter.rb +39 -59
  14. data/lib/dm-core/adapters/postgres_adapter.rb +0 -1
  15. data/lib/dm-core/adapters/sqlite3_adapter.rb +5 -0
  16. data/lib/dm-core/adapters/sqlserver_adapter.rb +114 -0
  17. data/lib/dm-core/adapters/yaml_adapter.rb +0 -5
  18. data/lib/dm-core/associations/many_to_many.rb +118 -56
  19. data/lib/dm-core/associations/many_to_one.rb +48 -21
  20. data/lib/dm-core/associations/one_to_many.rb +8 -30
  21. data/lib/dm-core/associations/one_to_one.rb +1 -5
  22. data/lib/dm-core/associations/relationship.rb +89 -97
  23. data/lib/dm-core/collection.rb +299 -184
  24. data/lib/dm-core/core_ext/enumerable.rb +28 -0
  25. data/lib/dm-core/core_ext/kernel.rb +0 -2
  26. data/lib/dm-core/migrations.rb +314 -170
  27. data/lib/dm-core/model.rb +97 -66
  28. data/lib/dm-core/model/descendant_set.rb +1 -1
  29. data/lib/dm-core/model/hook.rb +0 -3
  30. data/lib/dm-core/model/property.rb +7 -10
  31. data/lib/dm-core/model/relationship.rb +79 -26
  32. data/lib/dm-core/model/scope.rb +3 -4
  33. data/lib/dm-core/property.rb +152 -90
  34. data/lib/dm-core/property_set.rb +18 -37
  35. data/lib/dm-core/query.rb +452 -153
  36. data/lib/dm-core/query/conditions/comparison.rb +266 -173
  37. data/lib/dm-core/query/conditions/operation.rb +499 -57
  38. data/lib/dm-core/query/direction.rb +0 -3
  39. data/lib/dm-core/query/operator.rb +0 -4
  40. data/lib/dm-core/query/path.rb +10 -12
  41. data/lib/dm-core/query/sort.rb +4 -10
  42. data/lib/dm-core/repository.rb +10 -6
  43. data/lib/dm-core/resource.rb +343 -148
  44. data/lib/dm-core/spec/adapter_shared_spec.rb +17 -1
  45. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +277 -17
  46. data/lib/dm-core/support/chainable.rb +0 -2
  47. data/lib/dm-core/support/equalizer.rb +27 -3
  48. data/lib/dm-core/transaction.rb +75 -75
  49. data/lib/dm-core/type.rb +19 -5
  50. data/lib/dm-core/types/discriminator.rb +4 -4
  51. data/lib/dm-core/types/object.rb +2 -7
  52. data/lib/dm-core/types/paranoid_boolean.rb +8 -2
  53. data/lib/dm-core/types/paranoid_datetime.rb +8 -2
  54. data/lib/dm-core/version.rb +1 -1
  55. data/script/performance.rb +7 -7
  56. data/script/profile.rb +6 -6
  57. data/spec/lib/collection_helpers.rb +2 -2
  58. data/spec/lib/pending_helpers.rb +22 -3
  59. data/spec/lib/rspec_immediate_feedback_formatter.rb +1 -0
  60. data/spec/public/associations/many_to_many_spec.rb +6 -4
  61. data/spec/public/associations/many_to_one_spec.rb +10 -1
  62. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +39 -0
  63. data/spec/public/associations/one_to_many_spec.rb +4 -3
  64. data/spec/public/associations/one_to_one_spec.rb +19 -1
  65. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +45 -0
  66. data/spec/public/collection_spec.rb +4 -3
  67. data/spec/public/migrations_spec.rb +144 -0
  68. data/spec/public/model/relationship_spec.rb +115 -55
  69. data/spec/public/model_spec.rb +13 -13
  70. data/spec/public/property/object_spec.rb +106 -0
  71. data/spec/public/property_spec.rb +18 -14
  72. data/spec/public/resource_spec.rb +10 -1
  73. data/spec/public/sel_spec.rb +16 -49
  74. data/spec/public/setup_spec.rb +1 -1
  75. data/spec/public/shared/association_collection_shared_spec.rb +6 -14
  76. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  77. data/spec/public/shared/collection_shared_spec.rb +214 -217
  78. data/spec/public/shared/finder_shared_spec.rb +259 -365
  79. data/spec/public/shared/resource_shared_spec.rb +524 -248
  80. data/spec/public/transaction_spec.rb +27 -3
  81. data/spec/public/types/discriminator_spec.rb +1 -1
  82. data/spec/rcov.opts +6 -0
  83. data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +17 -0
  84. data/spec/semipublic/associations/many_to_one_spec.rb +3 -20
  85. data/spec/semipublic/associations_spec.rb +2 -2
  86. data/spec/semipublic/collection_spec.rb +0 -32
  87. data/spec/semipublic/model_spec.rb +96 -0
  88. data/spec/semipublic/property_spec.rb +3 -3
  89. data/spec/semipublic/query/conditions/comparison_spec.rb +1719 -0
  90. data/spec/semipublic/query/conditions/operation_spec.rb +1292 -0
  91. data/spec/semipublic/query_spec.rb +1285 -144
  92. data/spec/semipublic/resource_spec.rb +0 -24
  93. data/spec/semipublic/shared/resource_shared_spec.rb +103 -38
  94. data/spec/spec.opts +1 -1
  95. data/spec/spec_helper.rb +15 -6
  96. data/tasks/ci.rake +1 -0
  97. data/tasks/metrics.rake +37 -0
  98. data/tasks/spec.rake +41 -0
  99. data/tasks/yard.rake +9 -0
  100. data/tasks/yardstick.rake +19 -0
  101. metadata +99 -29
  102. data/CONTRIBUTING +0 -51
  103. data/FAQ +0 -93
  104. data/History.txt +0 -27
  105. data/MIT-LICENSE +0 -22
  106. data/Manifest.txt +0 -121
  107. data/QUICKLINKS +0 -11
  108. data/SPECS +0 -35
  109. data/TODO +0 -1
  110. data/spec/semipublic/query/conditions_spec.rb +0 -528
  111. data/tasks/ci.rb +0 -24
  112. data/tasks/dm.rb +0 -58
  113. data/tasks/doc.rb +0 -17
  114. data/tasks/gemspec.rb +0 -23
  115. data/tasks/hoe.rb +0 -45
  116. data/tasks/install.rb +0 -18
@@ -0,0 +1,1292 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper'))
2
+
3
+ module OperationMatchers
4
+ class HaveValidParent
5
+ def matches?(target)
6
+ stack = []
7
+ stack << [ target, target.operands ] if target.respond_to?(:operands)
8
+
9
+ while node = stack.pop
10
+ @expected, operands = node
11
+
12
+ operands.each do |operand|
13
+ @target = operand
14
+ @actual = @target.parent
15
+
16
+ return false unless @actual.equal?(@expected)
17
+
18
+ if @target.respond_to?(:operands)
19
+ stack << [ @target, @target.operands ]
20
+ end
21
+ end
22
+ end
23
+
24
+ true
25
+ end
26
+
27
+ def failure_message
28
+ <<-OUTPUT
29
+ expected: #{@expected.inspect}
30
+ got: #{@actual.inspect}
31
+ OUTPUT
32
+ end
33
+ end
34
+
35
+ def have_operands_with_valid_parent
36
+ HaveValidParent.new
37
+ end
38
+ end
39
+
40
+ shared_examples_for 'DataMapper::Query::Conditions::AbstractOperation' do
41
+ before :all do
42
+ module ::Blog
43
+ class Article
44
+ include DataMapper::Resource
45
+
46
+ property :id, Serial
47
+ property :title, String, :required => true
48
+ end
49
+ end
50
+
51
+ @model = Blog::Article
52
+ end
53
+
54
+ before do
55
+ class ::OtherOperation < DataMapper::Query::Conditions::AbstractOperation
56
+ slug :other
57
+ end
58
+ end
59
+
60
+ before do
61
+ @comparison = DataMapper::Query::Conditions::Comparison.new(:eql, @model.properties[:title], 'A title')
62
+ @and_operation = DataMapper::Query::Conditions::Operation.new(:and)
63
+ @or_operation = DataMapper::Query::Conditions::Operation.new(:or)
64
+ @not_operation = DataMapper::Query::Conditions::Operation.new(:not)
65
+ @null_operation = DataMapper::Query::Conditions::Operation.new(:null)
66
+ @other = OtherOperation.new
67
+ end
68
+
69
+ it { @operation.class.should respond_to(:new) }
70
+
71
+ describe '.new' do
72
+ describe 'with no arguments' do
73
+ subject { @operation.class.new }
74
+
75
+ it { should be_kind_of(@operation.class) }
76
+ end
77
+
78
+ describe 'with arguments' do
79
+ subject { @operation.class.new(@comparison) }
80
+
81
+ it { should be_kind_of(@operation.class) }
82
+ end
83
+ end
84
+
85
+ it { @operation.class.should respond_to(:slug) }
86
+
87
+ describe '.slug' do
88
+ describe 'with no arguments' do
89
+ subject { @operation.class.slug }
90
+
91
+ it { should == @slug }
92
+ end
93
+
94
+ describe 'with an argument' do
95
+ subject { @operation.class.slug(:other) }
96
+
97
+ it { should == :other }
98
+
99
+ # reset the AndOperation slug
100
+ after { @operation.class.slug(@slug) }
101
+ end
102
+ end
103
+
104
+ it { should respond_to(:==) }
105
+
106
+ describe '#==' do
107
+ describe 'when the other AbstractOperation is equal' do
108
+ # artifically modify the object so #eql? will throw an
109
+ # exception if the equal? branch is not followed when heckling
110
+ before { @operation.meta_class.send(:undef_method, :slug) }
111
+
112
+ subject { @operation == @operation }
113
+
114
+ it { should be_true }
115
+ end
116
+
117
+ describe 'when the other AbstractOperation is the same class' do
118
+ subject { @operation == DataMapper::Query::Conditions::Operation.new(@slug) }
119
+
120
+ it { should be_true }
121
+ end
122
+
123
+ describe 'when the other AbstractOperation is a different class, with the same slug' do
124
+ before { @other.class.slug(@slug) }
125
+
126
+ subject { @operation == @other }
127
+
128
+ it { should be_true }
129
+
130
+ # reset the OtherOperation slug
131
+ after { @other.class.slug(:other) }
132
+ end
133
+
134
+ describe 'when the other AbstractOperation is the same class, with different operands' do
135
+ subject { @operation == DataMapper::Query::Conditions::Operation.new(@slug, @comparison) }
136
+
137
+ it { should be_false }
138
+ end
139
+ end
140
+
141
+ it { should respond_to(:<<) }
142
+
143
+ describe '#<<' do
144
+ describe 'with a NullOperation' do
145
+ subject { @operation << @null_operation }
146
+
147
+ it { should equal(@operation) }
148
+
149
+ it 'should merge the operand' do
150
+ subject.to_a.should == [ @null_operation.class.new ]
151
+ end
152
+
153
+ it { should have_operands_with_valid_parent }
154
+ end
155
+
156
+ describe 'with a duplicate operand' do
157
+ before { @operation << @comparison.dup }
158
+
159
+ subject { @operation << @comparison.dup }
160
+
161
+ it { should equal(@operation) }
162
+
163
+ it 'should have unique operands' do
164
+ subject.to_a.should == [ @comparison ]
165
+ end
166
+
167
+ it { should have_operands_with_valid_parent }
168
+ end
169
+
170
+ describe 'with an invalid operand' do
171
+ subject { @operation << '' }
172
+
173
+ it { method(:subject).should raise_error(ArgumentError, '+operand+ should be DataMapper::Query::Conditions::AbstractOperation or DataMapper::Query::Conditions::AbstractComparison or Array, but was String') }
174
+ end
175
+ end
176
+
177
+ it { should respond_to(:children) }
178
+
179
+ describe '#children' do
180
+ subject { @operation.children }
181
+
182
+ it { should be_kind_of(Set) }
183
+
184
+ it { should be_empty }
185
+
186
+ it { should equal(@operation.operands) }
187
+ end
188
+
189
+ it { should respond_to(:clear) }
190
+
191
+ describe '#clear' do
192
+ before do
193
+ @operation << @other
194
+ @operation.should_not be_empty
195
+ end
196
+
197
+ subject { @operation.clear }
198
+
199
+ it { should equal(@operation) }
200
+
201
+ it 'should clear the operands' do
202
+ subject.should be_empty
203
+ end
204
+ end
205
+
206
+ [ :difference, :- ].each do |method|
207
+ it { should respond_to(method) }
208
+
209
+ describe "##{method}" do
210
+ subject { @operation.send(method, @comparison) }
211
+
212
+ it { should eql(@not_operation.class.new(@comparison)) }
213
+ end
214
+ end
215
+
216
+ it { should respond_to(:dup) }
217
+
218
+ describe '#dup' do
219
+ subject { @operation.dup }
220
+
221
+ it { should_not equal(@operation) }
222
+
223
+ it { subject.to_a.should == @operation.to_a }
224
+ end
225
+
226
+ it { should respond_to(:each) }
227
+
228
+ describe '#each' do
229
+ before do
230
+ @yield = []
231
+ @operation << @other
232
+ end
233
+
234
+ subject { @operation.each { |operand| @yield << operand } }
235
+
236
+ it { should equal(@operation) }
237
+
238
+ it 'should yield to every operand' do
239
+ subject
240
+ @yield.should == [ @other ]
241
+ end
242
+ end
243
+
244
+ it { should respond_to(:eql?) }
245
+
246
+ describe '#eql?' do
247
+ describe 'when the other AbstractOperation is equal' do
248
+ # artifically modify the object so #eql? will throw an
249
+ # exception if the equal? branch is not followed when heckling
250
+ before { @operation.meta_class.send(:undef_method, :slug) }
251
+
252
+ subject { @operation.eql?(@operation) }
253
+
254
+ it { should be_true }
255
+ end
256
+
257
+ describe 'when the other AbstractOperation is the same class' do
258
+ subject { @operation.eql?(DataMapper::Query::Conditions::Operation.new(@slug)) }
259
+
260
+ it { should be_true }
261
+ end
262
+
263
+ describe 'when the other AbstractOperation is a different class' do
264
+ subject { @operation.eql?(DataMapper::Query::Conditions::Operation.new(:other)) }
265
+
266
+ it { should be_false }
267
+ end
268
+
269
+ describe 'when the other AbstractOperation is the same class, with different operands' do
270
+ subject { @operation.eql?(DataMapper::Query::Conditions::Operation.new(@slug, @comparison)) }
271
+
272
+ it { should be_false }
273
+ end
274
+
275
+ describe 'when operations contain more than one operand' do
276
+ before do
277
+ @operation << DataMapper::Query::Conditions::Operation.new(:and, @comparison, @other)
278
+ @other = @operation.dup
279
+ end
280
+
281
+ it { should be_true }
282
+ end
283
+ end
284
+
285
+ it { should respond_to(:hash) }
286
+
287
+ describe '#hash' do
288
+ describe 'with operands' do
289
+ before do
290
+ @operation << @comparison
291
+ end
292
+
293
+ subject { @operation.hash }
294
+
295
+ it 'should match the same AbstractOperation with the same operands' do
296
+ should == DataMapper::Query::Conditions::Operation.new(@slug, @comparison.dup).hash
297
+ end
298
+
299
+ it 'should not match the same AbstractOperation with different operands' do
300
+ should_not == DataMapper::Query::Conditions::Operation.new(@slug).hash
301
+ end
302
+
303
+ it 'should not match a different AbstractOperation with the same operands' do
304
+ should_not == @other.class.new(@comparison.dup).hash
305
+ end
306
+
307
+ it 'should not match a different AbstractOperation with different operands' do
308
+ should_not == DataMapper::Query::Conditions::Operation.new(:or).hash
309
+ end
310
+ end
311
+ end
312
+
313
+ [ :intersection, :& ].each do |method|
314
+ it { should respond_to(method) }
315
+
316
+ describe "##{method}" do
317
+ subject { @operation.send(method, @other) }
318
+
319
+ it { should eql(@and_operation) }
320
+ end
321
+ end
322
+
323
+ it { should respond_to(:merge) }
324
+
325
+ describe '#merge' do
326
+ describe 'with a NullOperation' do
327
+ subject { @operation.merge([ @null_operation ]) }
328
+
329
+ it { should equal(@operation) }
330
+
331
+ it 'should merge the operand' do
332
+ subject.to_a.should == [ @null_operation.class.new ]
333
+ end
334
+
335
+ it { should have_operands_with_valid_parent }
336
+ end
337
+
338
+ describe 'with a duplicate operand' do
339
+ before { @operation << @comparison.dup }
340
+
341
+ subject { @operation.merge([ @comparison.dup ]) }
342
+
343
+ it { should equal(@operation) }
344
+
345
+ it 'should have unique operands' do
346
+ subject.to_a.should == [ @comparison ]
347
+ end
348
+
349
+ it { should have_operands_with_valid_parent }
350
+ end
351
+
352
+ describe 'with an invalid operand' do
353
+ subject { @operation.merge([ '' ]) }
354
+
355
+ it { method(:subject).should raise_error(ArgumentError) }
356
+ end
357
+ end
358
+
359
+ it { should respond_to(:operands) }
360
+
361
+ describe '#operands' do
362
+ subject { @operation.operands }
363
+
364
+ it { should be_kind_of(Set) }
365
+
366
+ it { should be_empty }
367
+
368
+ it { should equal(@operation.children) }
369
+ end
370
+
371
+ it { should respond_to(:parent) }
372
+
373
+ describe '#parent' do
374
+ describe 'when there is no parent' do
375
+ subject { @operation.parent }
376
+
377
+ it { should be_nil }
378
+ end
379
+
380
+ describe 'when there is a parent' do
381
+ before { @other << @operation }
382
+
383
+ subject { @operation.parent }
384
+
385
+ it { should equal(@other) }
386
+ end
387
+ end
388
+
389
+ it { should respond_to(:parent=) }
390
+
391
+ describe '#parent=' do
392
+ subject { @operation.parent = @other }
393
+
394
+ it { should equal(@other) }
395
+
396
+ it 'should change the parent' do
397
+ method(:subject).should change(@operation, :parent).
398
+ from(nil).
399
+ to(@other)
400
+ end
401
+ end
402
+
403
+ [ :union, :|, :+ ].each do |method|
404
+ it { should respond_to(method) }
405
+
406
+ describe "##{method}" do
407
+ subject { @operation.send(method, @null_operation) }
408
+
409
+ it { should eql(@null_operation) }
410
+ end
411
+ end
412
+
413
+ it { should respond_to(:valid?) }
414
+
415
+ describe '#valid?' do
416
+ subject { @operation.valid? }
417
+
418
+ describe 'with no operands' do
419
+ it { should be_false }
420
+ end
421
+
422
+ describe 'with an operand that responds to #valid?' do
423
+ describe 'and is valid' do
424
+ before do
425
+ @operation << @comparison
426
+ end
427
+
428
+ it { should be_true }
429
+ end
430
+
431
+ describe 'and is not valid' do
432
+ before do
433
+ @operation << @or_operation.dup
434
+ end
435
+
436
+ it { should be_false }
437
+ end
438
+ end
439
+
440
+ describe 'with an operand that does not respond to #valid?' do
441
+ before do
442
+ @operation << [ 'raw = 1' ]
443
+ end
444
+
445
+ it { should be_true }
446
+ end
447
+ end
448
+ end
449
+
450
+ describe DataMapper::Query::Conditions::Operation do
451
+ it { DataMapper::Query::Conditions::Operation.should respond_to(:new) }
452
+
453
+ describe '.new' do
454
+ {
455
+ :and => DataMapper::Query::Conditions::AndOperation,
456
+ :or => DataMapper::Query::Conditions::OrOperation,
457
+ :not => DataMapper::Query::Conditions::NotOperation,
458
+ :null => DataMapper::Query::Conditions::NullOperation,
459
+ }.each do |slug, klass|
460
+ describe "with a slug #{slug.inspect}" do
461
+ subject { DataMapper::Query::Conditions::Operation.new(slug) }
462
+
463
+ it { should be_kind_of(klass) }
464
+
465
+ it { subject.should be_empty }
466
+ end
467
+ end
468
+
469
+ describe 'with an invalid slug' do
470
+ subject { DataMapper::Query::Conditions::Operation.new(:invalid) }
471
+
472
+ it { method(:subject).should raise_error(ArgumentError, 'No Operation class for :invalid has been defined') }
473
+ end
474
+
475
+ describe 'with operands' do
476
+ before { @or_operation = DataMapper::Query::Conditions::Operation.new(:or) }
477
+
478
+ subject { DataMapper::Query::Conditions::Operation.new(:and, @or_operation) }
479
+
480
+ it { should be_kind_of(DataMapper::Query::Conditions::AndOperation) }
481
+
482
+ it 'should set the operands' do
483
+ subject.to_a.should == [ @or_operation ]
484
+ end
485
+ end
486
+ end
487
+ end
488
+
489
+ describe DataMapper::Query::Conditions::AndOperation do
490
+ include OperationMatchers
491
+
492
+ it_should_behave_like 'DataMapper::Query::Conditions::AbstractOperation'
493
+
494
+ before do
495
+ @operation = @and_operation
496
+ @slug = @operation.slug
497
+ end
498
+
499
+ it { should respond_to(:<<) }
500
+
501
+ describe '#<<' do
502
+ [
503
+ DataMapper::Query::Conditions::AndOperation,
504
+ DataMapper::Query::Conditions::OrOperation,
505
+ DataMapper::Query::Conditions::NotOperation,
506
+ ].each do |klass|
507
+ describe "with an #{klass.name.split('::').last}" do
508
+ before do
509
+ @other = klass.new(@comparison)
510
+ end
511
+
512
+ subject { @operation << @other }
513
+
514
+ it { should equal(@operation) }
515
+
516
+ if klass == DataMapper::Query::Conditions::AndOperation
517
+ it 'should flatten and merge the operand' do
518
+ subject.to_a.should == @other.operands.to_a
519
+ end
520
+ else
521
+ it 'should merge the operand' do
522
+ subject.to_a.should == [ @other ]
523
+ end
524
+ end
525
+
526
+ it { should have_operands_with_valid_parent }
527
+ end
528
+ end
529
+ end
530
+
531
+ it { should respond_to(:negated?) }
532
+
533
+ describe '#negated?' do
534
+ describe 'with a negated parent' do
535
+ before do
536
+ @not_operation.class.new(@operation)
537
+ end
538
+
539
+ subject { @operation.negated? }
540
+
541
+ it { should be_true }
542
+ end
543
+
544
+ describe 'with a not negated parent' do
545
+ before do
546
+ @or_operation.class.new(@operation)
547
+ end
548
+
549
+ subject { @operation.negated? }
550
+
551
+ it { should be_false }
552
+ end
553
+
554
+ describe 'after memoizing the negation, and switching parents' do
555
+ before do
556
+ @or_operation.class.new(@operation)
557
+ @operation.should_not be_negated
558
+ @not_operation.class.new(@operation)
559
+ end
560
+
561
+ subject { @operation.negated? }
562
+
563
+ it { should be_true }
564
+ end
565
+ end
566
+
567
+ it { should respond_to(:matches?) }
568
+
569
+ describe '#matches?' do
570
+ before do
571
+ @operation << @comparison << @comparison.class.new(@model.properties[:id], 1)
572
+ end
573
+
574
+ supported_by :all do
575
+ describe 'with a matching Hash' do
576
+ subject { @operation.matches?('title' => 'A title', 'id' => 1) }
577
+
578
+ it { should be_true }
579
+ end
580
+
581
+ describe 'with a not matching Hash' do
582
+ subject { @operation.matches?('title' => 'Not matching', 'id' => 1) }
583
+
584
+ it { should be_false }
585
+ end
586
+
587
+ describe 'with a matching Resource' do
588
+ subject { @operation.matches?(@model.new(:title => 'A title', :id => 1)) }
589
+
590
+ it { should be_true }
591
+ end
592
+
593
+ describe 'with a not matching Resource' do
594
+ subject { @operation.matches?(@model.new(:title => 'Not matching', :id => 1)) }
595
+
596
+ it { should be_false }
597
+ end
598
+
599
+ describe 'with a raw condition' do
600
+ before do
601
+ @operation = @operation.class.new([ 'title = ?', 'Another title' ])
602
+ end
603
+
604
+ subject { @operation.matches?('title' => 'A title', 'id' => 1) }
605
+
606
+ it { should be_true }
607
+ end
608
+ end
609
+ end
610
+
611
+ it { should respond_to(:minimize) }
612
+
613
+ describe '#minimize' do
614
+ subject { @operation.minimize }
615
+
616
+ describe 'with one empty operand' do
617
+ before do
618
+ @operation << @other
619
+ end
620
+
621
+ it { should equal(@operation) }
622
+
623
+ it { subject.should be_empty }
624
+ end
625
+
626
+ describe 'with more than one operation' do
627
+ before do
628
+ @operation.merge([ @comparison, @not_operation.class.new(@comparison) ])
629
+ end
630
+
631
+ it { should equal(@operation) }
632
+
633
+ it { subject.to_a.should =~ [ @comparison, @not_operation.class.new(@comparison) ] }
634
+ end
635
+
636
+ describe 'with one non-empty operand' do
637
+ before do
638
+ @operation << @comparison
639
+ end
640
+
641
+ it { should == @comparison }
642
+ end
643
+
644
+ describe 'with one null operation' do
645
+ before do
646
+ @operation << @null_operation
647
+ end
648
+
649
+ it { should eql(@null_operation) }
650
+ end
651
+
652
+ describe 'with one null operation and one non-null operation' do
653
+ before do
654
+ @operation.merge([ @null_operation, @comparison ])
655
+ end
656
+
657
+ it { should eql(@comparison) }
658
+ end
659
+ end
660
+
661
+ it { should respond_to(:merge) }
662
+
663
+ describe '#merge' do
664
+ [
665
+ DataMapper::Query::Conditions::AndOperation,
666
+ DataMapper::Query::Conditions::OrOperation,
667
+ DataMapper::Query::Conditions::NotOperation,
668
+ ].each do |klass|
669
+ describe "with an #{klass.name.split('::').last}" do
670
+ before do
671
+ @other = klass.new(@comparison)
672
+ end
673
+
674
+ subject { @operation.merge([ @other ]) }
675
+
676
+ it { should equal(@operation) }
677
+
678
+ if klass == DataMapper::Query::Conditions::AndOperation
679
+ it 'should flatten and merge the operand' do
680
+ subject.to_a.should == @other.operands.to_a
681
+ end
682
+ else
683
+ it 'should merge the operand' do
684
+ subject.to_a.should == [ @other ]
685
+ end
686
+ end
687
+
688
+ it { should have_operands_with_valid_parent }
689
+ end
690
+ end
691
+ end
692
+
693
+ it { should respond_to(:to_s) }
694
+
695
+ describe '#to_s' do
696
+ describe 'with no operands' do
697
+ subject { @operation.to_s }
698
+
699
+ it { should eql('') }
700
+ end
701
+
702
+ describe 'with operands' do
703
+ before do
704
+ @not_operation << @comparison.dup
705
+ @operation << @comparison << @not_operation
706
+ end
707
+
708
+ subject { @operation.to_s }
709
+
710
+ it { should eql('(NOT(title = "A title") AND title = "A title")') }
711
+ end
712
+ end
713
+
714
+ it { should respond_to(:valid?) }
715
+
716
+ describe '#valid?' do
717
+ describe 'with one valid operand, and one invalid operand' do
718
+ before do
719
+ @operation << @comparison
720
+ @operation << DataMapper::Query::Conditions::Comparison.new(:in, @model.properties[:id], [])
721
+ end
722
+
723
+ subject { @operation.valid? }
724
+
725
+ it { should be_false }
726
+ end
727
+
728
+ describe 'with one invalid operand' do
729
+ before do
730
+ @operation << DataMapper::Query::Conditions::Comparison.new(:in, @model.properties[:id], [])
731
+ end
732
+
733
+ subject { @operation.valid? }
734
+
735
+ it { should be_false }
736
+ end
737
+ end
738
+ end
739
+
740
+ describe DataMapper::Query::Conditions::OrOperation do
741
+ include OperationMatchers
742
+
743
+ it_should_behave_like 'DataMapper::Query::Conditions::AbstractOperation'
744
+
745
+ before do
746
+ @operation = @or_operation
747
+ @slug = @operation.slug
748
+ end
749
+
750
+ it { should respond_to(:<<) }
751
+
752
+ describe '#<<' do
753
+ [
754
+ DataMapper::Query::Conditions::AndOperation,
755
+ DataMapper::Query::Conditions::OrOperation,
756
+ DataMapper::Query::Conditions::NotOperation,
757
+ ].each do |klass|
758
+ describe "with an #{klass.name.split('::').last}" do
759
+ before do
760
+ @other = klass.new(@comparison)
761
+ end
762
+
763
+ subject { @operation << @other }
764
+
765
+ it { should equal(@operation) }
766
+
767
+ if klass == DataMapper::Query::Conditions::OrOperation
768
+ it 'should flatten and merge the operand' do
769
+ subject.to_a.should == @other.operands.to_a
770
+ end
771
+ else
772
+ it 'should merge the operand' do
773
+ subject.to_a.should == [ @other ]
774
+ end
775
+ end
776
+
777
+ it { should have_operands_with_valid_parent }
778
+ end
779
+ end
780
+ end
781
+
782
+ it { should respond_to(:negated?) }
783
+
784
+ describe '#negated?' do
785
+ describe 'with a negated parent' do
786
+ before do
787
+ @not_operation.class.new(@operation)
788
+ end
789
+
790
+ subject { @operation.negated? }
791
+
792
+ it { should be_true }
793
+ end
794
+
795
+ describe 'with a not negated parent' do
796
+ before do
797
+ @and_operation.class.new(@operation)
798
+ end
799
+
800
+ subject { @operation.negated? }
801
+
802
+ it { should be_false }
803
+ end
804
+
805
+ describe 'after memoizing the negation, and switching parents' do
806
+ before do
807
+ @or_operation.class.new(@operation)
808
+ @operation.should_not be_negated
809
+ @not_operation.class.new(@operation)
810
+ end
811
+
812
+ subject { @operation.negated? }
813
+
814
+ it { should be_true }
815
+ end
816
+ end
817
+
818
+ it { should respond_to(:matches?) }
819
+
820
+ describe '#matches?' do
821
+ before do
822
+ @operation << @comparison << @comparison.class.new(@model.properties[:id], 1)
823
+ end
824
+
825
+ supported_by :all do
826
+ describe 'with a matching Hash' do
827
+ subject { @operation.matches?('title' => 'A title', 'id' => 2) }
828
+
829
+ it { should be_true }
830
+ end
831
+
832
+ describe 'with a not matching Hash' do
833
+ subject { @operation.matches?('title' => 'Not matching', 'id' => 2) }
834
+
835
+ it { should be_false }
836
+ end
837
+
838
+ describe 'with a matching Resource' do
839
+ subject { @operation.matches?(@model.new(:title => 'A title', :id => 2)) }
840
+
841
+ it { should be_true }
842
+ end
843
+
844
+ describe 'with a not matching Resource' do
845
+ subject { @operation.matches?(@model.new(:title => 'Not matching', :id => 2)) }
846
+
847
+ it { should be_false }
848
+ end
849
+
850
+ describe 'with a raw condition' do
851
+ before do
852
+ @operation = @operation.class.new([ 'title = ?', 'Another title' ])
853
+ end
854
+
855
+ subject { @operation.matches?('title' => 'A title', 'id' => 2) }
856
+
857
+ it { should be_true }
858
+ end
859
+ end
860
+ end
861
+
862
+ it { should respond_to(:minimize) }
863
+
864
+ describe '#minimize' do
865
+ subject { @operation.minimize }
866
+
867
+ describe 'with one empty operand' do
868
+ before do
869
+ @operation << @other
870
+ end
871
+
872
+ it { should equal(@operation) }
873
+
874
+ it { subject.should be_empty }
875
+ end
876
+
877
+ describe 'with more than one operation' do
878
+ before do
879
+ @operation.merge([ @comparison, @not_operation.class.new(@comparison) ])
880
+ end
881
+
882
+ it { should equal(@operation) }
883
+
884
+ it { subject.to_a.should =~ [ @comparison, @not_operation.class.new(@comparison) ] }
885
+ end
886
+
887
+ describe 'with one non-empty operand' do
888
+ before do
889
+ @operation << @comparison
890
+ end
891
+
892
+ it { should == @comparison }
893
+ end
894
+
895
+ describe 'with one null operation' do
896
+ before do
897
+ @operation << @null_operation
898
+ end
899
+
900
+ it { should eql(@null_operation) }
901
+ end
902
+ end
903
+
904
+ it { should respond_to(:merge) }
905
+
906
+ describe '#merge' do
907
+ [
908
+ DataMapper::Query::Conditions::AndOperation,
909
+ DataMapper::Query::Conditions::OrOperation,
910
+ DataMapper::Query::Conditions::NotOperation,
911
+ ].each do |klass|
912
+ describe "with an #{klass.name.split('::').last}" do
913
+ before do
914
+ @other = klass.new(@comparison)
915
+ end
916
+
917
+ subject { @operation.merge([ @other ]) }
918
+
919
+ it { should equal(@operation) }
920
+
921
+ if klass == DataMapper::Query::Conditions::OrOperation
922
+ it 'should flatten and merge the operand' do
923
+ subject.to_a.should == @other.operands.to_a
924
+ end
925
+ else
926
+ it 'should merge the operand' do
927
+ subject.to_a.should == [ @other ]
928
+ end
929
+ end
930
+
931
+ it { should have_operands_with_valid_parent }
932
+ end
933
+ end
934
+ end
935
+
936
+ it { should respond_to(:valid?) }
937
+
938
+ describe '#valid?' do
939
+ describe 'with one valid operand, and one invalid operand' do
940
+ before do
941
+ @operation << @comparison
942
+ @operation << DataMapper::Query::Conditions::Comparison.new(:in, @model.properties[:id], [])
943
+ end
944
+
945
+ subject { @operation.valid? }
946
+
947
+ it { should be_true }
948
+ end
949
+
950
+ describe 'with one invalid operand' do
951
+ before do
952
+ @operation << DataMapper::Query::Conditions::Comparison.new(:in, @model.properties[:id], [])
953
+ end
954
+
955
+ subject { @operation.valid? }
956
+
957
+ it { should be_false }
958
+ end
959
+ end
960
+ end
961
+
962
+ describe DataMapper::Query::Conditions::NotOperation do
963
+ include OperationMatchers
964
+
965
+ it_should_behave_like 'DataMapper::Query::Conditions::AbstractOperation'
966
+
967
+ before do
968
+ @operation = @not_operation
969
+ @slug = @operation.slug
970
+ end
971
+
972
+ it { should respond_to(:<<) }
973
+
974
+ describe '#<<' do
975
+ [
976
+ DataMapper::Query::Conditions::AndOperation,
977
+ DataMapper::Query::Conditions::OrOperation,
978
+ DataMapper::Query::Conditions::NotOperation,
979
+ ].each do |klass|
980
+ describe "with an #{klass.name.split('::').last}" do
981
+ before do
982
+ @other = klass.new(@comparison)
983
+ end
984
+
985
+ subject { @operation << @other }
986
+
987
+ it { should equal(@operation) }
988
+
989
+ it 'should merge the operand' do
990
+ subject.to_a.should == [ @other ]
991
+ end
992
+
993
+ it { should have_operands_with_valid_parent }
994
+ end
995
+ end
996
+
997
+ describe 'with more than one operand' do
998
+ subject { @operation << @comparison << @other }
999
+
1000
+ it { method(:subject).should raise_error(ArgumentError) }
1001
+ end
1002
+
1003
+ describe 'with self as an operand' do
1004
+ subject { @operation << @operation }
1005
+
1006
+ it { method(:subject).should raise_error(ArgumentError, 'cannot append operand to itself') }
1007
+ end
1008
+ end
1009
+
1010
+ it { should respond_to(:negated?) }
1011
+
1012
+ describe '#negated?' do
1013
+ describe 'with a negated parent' do
1014
+ before do
1015
+ @not_operation.class.new(@operation)
1016
+ end
1017
+
1018
+ subject { @operation.negated? }
1019
+
1020
+ it { should be_false }
1021
+ end
1022
+
1023
+ describe 'with a not negated parent' do
1024
+ before do
1025
+ @or_operation.class.new(@operation)
1026
+ end
1027
+
1028
+ subject { @operation.negated? }
1029
+
1030
+ it { should be_true }
1031
+ end
1032
+
1033
+ describe 'after memoizing the negation, and switching parents' do
1034
+ before do
1035
+ @or_operation.class.new(@operation)
1036
+ @operation.should be_negated
1037
+ @not_operation.class.new(@operation)
1038
+ end
1039
+
1040
+ subject { @operation.negated? }
1041
+
1042
+ it { should be_false }
1043
+ end
1044
+ end
1045
+
1046
+ it { should respond_to(:matches?) }
1047
+
1048
+ describe '#matches?' do
1049
+ before do
1050
+ @operation << @comparison.class.new(@model.properties[:id], 1)
1051
+ end
1052
+
1053
+ supported_by :all do
1054
+ describe 'with a matching Hash' do
1055
+ subject { @operation.matches?('id' => 2) }
1056
+
1057
+ it { should be_true }
1058
+ end
1059
+
1060
+ describe 'with a not matching Hash' do
1061
+ subject { @operation.matches?('id' => 1) }
1062
+
1063
+ it { should be_false }
1064
+ end
1065
+
1066
+ describe 'with a matching Resource' do
1067
+ subject { @operation.matches?(@model.new(:id => 2)) }
1068
+
1069
+ it { should be_true }
1070
+ end
1071
+
1072
+ describe 'with a not matching Hash' do
1073
+ subject { @operation.matches?(@model.new(:id => 1)) }
1074
+
1075
+ it { should be_false }
1076
+ end
1077
+
1078
+ describe 'with a raw condition' do
1079
+ before do
1080
+ @operation = @operation.class.new([ 'title = ?', 'Another title' ])
1081
+ end
1082
+
1083
+ subject { @operation.matches?('id' => 2) }
1084
+
1085
+ it { should be_true }
1086
+ end
1087
+ end
1088
+ end
1089
+
1090
+ it { should respond_to(:merge) }
1091
+
1092
+ describe '#merge' do
1093
+ [
1094
+ DataMapper::Query::Conditions::AndOperation,
1095
+ DataMapper::Query::Conditions::OrOperation,
1096
+ DataMapper::Query::Conditions::NotOperation,
1097
+ ].each do |klass|
1098
+ describe "with an #{klass.name.split('::').last}" do
1099
+ before do
1100
+ @other = klass.new(@comparison)
1101
+ end
1102
+
1103
+ subject { @operation.merge([ @other ]) }
1104
+
1105
+ it { should equal(@operation) }
1106
+
1107
+ it 'should merge the operand' do
1108
+ subject.to_a.should == [ @other ]
1109
+ end
1110
+
1111
+ it { should have_operands_with_valid_parent }
1112
+ end
1113
+ end
1114
+
1115
+ describe 'with more than one operand' do
1116
+ subject { @operation.merge([ @comparison, @other ]) }
1117
+
1118
+ it { method(:subject).should raise_error(ArgumentError) }
1119
+ end
1120
+ end
1121
+
1122
+ it { should respond_to(:minimize) }
1123
+
1124
+ describe '#minimize' do
1125
+ subject { @operation.minimize }
1126
+
1127
+ describe 'when no operand' do
1128
+ it { should equal(@operation) }
1129
+ end
1130
+
1131
+ describe 'when operand is a NotOperation' do
1132
+ before do
1133
+ @operation << @not_operation.class.new(@comparison)
1134
+ end
1135
+
1136
+ it 'should remove the double negative' do
1137
+ should eql(@comparison)
1138
+ end
1139
+ end
1140
+
1141
+ describe 'when operand is not a NotOperation' do
1142
+ before do
1143
+ @operation << @comparison
1144
+ end
1145
+
1146
+ it { should equal(@operation) }
1147
+
1148
+ it { subject.to_a.should == [ @comparison ] }
1149
+ end
1150
+
1151
+ describe 'when operand is an empty operation' do
1152
+ before do
1153
+ @operation << @and_operation
1154
+ end
1155
+
1156
+ it { should equal(@operation) }
1157
+
1158
+ it { subject.should be_empty }
1159
+ end
1160
+
1161
+ describe 'when operand is an operation containing a Comparison' do
1162
+ before do
1163
+ @operation << @and_operation.class.new(@comparison)
1164
+ end
1165
+
1166
+ it { should equal(@operation) }
1167
+
1168
+ it { subject.to_a.should == [ @comparison ] }
1169
+ end
1170
+ end
1171
+
1172
+ it { should respond_to(:to_s) }
1173
+
1174
+ describe '#to_s' do
1175
+ describe 'with no operands' do
1176
+ subject { @operation.to_s }
1177
+
1178
+ it { should eql('') }
1179
+ end
1180
+
1181
+ describe 'with operands' do
1182
+ before do
1183
+ @operation << @comparison
1184
+ end
1185
+
1186
+ subject { @operation.to_s }
1187
+
1188
+ it { should eql('NOT(title = "A title")') }
1189
+ end
1190
+ end
1191
+
1192
+ it { should respond_to(:valid?) }
1193
+
1194
+ describe '#valid?' do
1195
+ describe 'with one invalid operand' do
1196
+ before do
1197
+ @operation << @not_operation.class.new(
1198
+ DataMapper::Query::Conditions::Comparison.new(:eql, @model.properties[:id], nil)
1199
+ )
1200
+ end
1201
+
1202
+ subject { @operation.valid? }
1203
+
1204
+ it { should be_false }
1205
+ end
1206
+ end
1207
+ end
1208
+
1209
+ describe DataMapper::Query::Conditions::NullOperation do
1210
+ include OperationMatchers
1211
+
1212
+ before :all do
1213
+ module ::Blog
1214
+ class Article
1215
+ include DataMapper::Resource
1216
+
1217
+ property :id, Serial
1218
+ property :title, String, :required => true
1219
+ end
1220
+ end
1221
+
1222
+ @model = Blog::Article
1223
+ end
1224
+
1225
+ before do
1226
+ @null_operation = DataMapper::Query::Conditions::Operation.new(:null)
1227
+ @operation = @null_operation
1228
+ @slug = @operation.slug
1229
+ end
1230
+
1231
+ it { should respond_to(:slug) }
1232
+
1233
+ describe '#slug' do
1234
+ subject { @operation.slug }
1235
+
1236
+ it { should == :null }
1237
+ end
1238
+
1239
+ it { should respond_to(:matches?) }
1240
+
1241
+ describe '#matches?' do
1242
+ describe 'with a Hash' do
1243
+ subject { @operation.matches?({}) }
1244
+
1245
+ it { should be_true }
1246
+ end
1247
+
1248
+ describe 'with a Resource' do
1249
+ subject { @operation.matches?(Blog::Article.new) }
1250
+
1251
+ it { should be_true }
1252
+ end
1253
+
1254
+ describe 'with any other Object' do
1255
+ subject { @operation.matches?(Object.new) }
1256
+
1257
+ it { should be_false }
1258
+ end
1259
+ end
1260
+
1261
+ it { should respond_to(:minimize) }
1262
+
1263
+ describe '#minimize' do
1264
+ subject { @operation.minimize }
1265
+
1266
+ it { should equal(@operation) }
1267
+ end
1268
+
1269
+ it { should respond_to(:valid?) }
1270
+
1271
+ describe '#valid?' do
1272
+ subject { @operation.valid? }
1273
+
1274
+ it { should be_true }
1275
+ end
1276
+
1277
+ it { should respond_to(:nil?) }
1278
+
1279
+ describe '#nil?' do
1280
+ subject { @operation.nil? }
1281
+
1282
+ it { should be_true }
1283
+ end
1284
+
1285
+ it { should respond_to(:inspect) }
1286
+
1287
+ describe '#inspect' do
1288
+ subject { @operation.inspect }
1289
+
1290
+ it { should == 'nil' }
1291
+ end
1292
+ end