dm-core 1.0.2 → 1.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (184) hide show
  1. data/Gemfile +28 -94
  2. data/LICENSE +1 -1
  3. data/README.rdoc +44 -11
  4. data/Rakefile +1 -7
  5. data/VERSION +1 -1
  6. data/dm-core.gemspec +398 -299
  7. data/lib/dm-core.rb +23 -13
  8. data/lib/dm-core/adapters/abstract_adapter.rb +1 -1
  9. data/lib/dm-core/associations/many_to_many.rb +1 -3
  10. data/lib/dm-core/associations/many_to_one.rb +54 -36
  11. data/lib/dm-core/associations/one_to_many.rb +1 -2
  12. data/lib/dm-core/associations/relationship.rb +11 -2
  13. data/lib/dm-core/collection.rb +3 -7
  14. data/lib/dm-core/core_ext/symbol.rb +1 -1
  15. data/lib/dm-core/identity_map.rb +0 -5
  16. data/lib/dm-core/model.rb +11 -21
  17. data/lib/dm-core/model/property.rb +43 -58
  18. data/lib/dm-core/model/relationship.rb +49 -44
  19. data/lib/dm-core/property.rb +106 -130
  20. data/lib/dm-core/property/date_time.rb +1 -3
  21. data/lib/dm-core/property/decimal.rb +11 -7
  22. data/lib/dm-core/property/integer.rb +2 -2
  23. data/lib/dm-core/property/lookup.rb +3 -16
  24. data/lib/dm-core/property/numeric.rb +3 -3
  25. data/lib/dm-core/property/object.rb +2 -11
  26. data/lib/dm-core/property/string.rb +1 -1
  27. data/lib/dm-core/property_set.rb +34 -54
  28. data/lib/dm-core/query.rb +85 -56
  29. data/lib/dm-core/query/conditions/comparison.rb +3 -6
  30. data/lib/dm-core/query/direction.rb +0 -4
  31. data/lib/dm-core/query/path.rb +22 -6
  32. data/lib/dm-core/relationship_set.rb +74 -0
  33. data/lib/dm-core/resource.rb +21 -32
  34. data/lib/dm-core/resource/state.rb +3 -4
  35. data/lib/dm-core/spec/lib/spec_helper.rb +1 -4
  36. data/lib/dm-core/spec/setup.rb +12 -5
  37. data/lib/dm-core/spec/shared/public/property_spec.rb +35 -21
  38. data/lib/dm-core/spec/shared/resource_spec.rb +1 -1
  39. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +9 -9
  40. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  41. data/lib/dm-core/support/deprecate.rb +1 -1
  42. data/lib/dm-core/support/descendant_set.rb +12 -5
  43. data/lib/dm-core/support/ordered_set.rb +382 -0
  44. data/lib/dm-core/support/subject_set.rb +252 -0
  45. data/lib/dm-core/version.rb +1 -1
  46. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +1 -2
  47. data/spec/public/associations/many_to_many_spec.rb +11 -9
  48. data/spec/public/associations/many_to_one_spec.rb +1 -1
  49. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +1 -1
  50. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +2 -2
  51. data/spec/public/associations/one_to_many_spec.rb +6 -5
  52. data/spec/public/associations/one_to_one_spec.rb +1 -1
  53. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +1 -1
  54. data/spec/public/collection_spec.rb +7 -6
  55. data/spec/public/finalize_spec.rb +1 -1
  56. data/spec/public/model/hook_spec.rb +4 -3
  57. data/spec/public/model/property_spec.rb +9 -3
  58. data/spec/public/model/relationship_spec.rb +2 -4
  59. data/spec/public/model_spec.rb +1 -1
  60. data/spec/public/property/binary_spec.rb +1 -1
  61. data/spec/public/property/boolean_spec.rb +1 -1
  62. data/spec/public/property/class_spec.rb +1 -1
  63. data/spec/public/property/date_spec.rb +1 -1
  64. data/spec/public/property/date_time_spec.rb +1 -1
  65. data/spec/public/property/decimal_spec.rb +7 -6
  66. data/spec/public/property/discriminator_spec.rb +1 -1
  67. data/spec/public/property/float_spec.rb +1 -1
  68. data/spec/public/property/integer_spec.rb +1 -1
  69. data/spec/public/property/object_spec.rb +2 -2
  70. data/spec/public/property/serial_spec.rb +1 -1
  71. data/spec/public/property/string_spec.rb +1 -1
  72. data/spec/public/property/text_spec.rb +6 -3
  73. data/spec/public/property/time_spec.rb +1 -1
  74. data/spec/public/property_spec.rb +13 -11
  75. data/spec/public/resource_spec.rb +43 -1
  76. data/spec/public/sel_spec.rb +1 -1
  77. data/spec/public/setup_spec.rb +1 -1
  78. data/spec/public/shared/collection_shared_spec.rb +6 -1
  79. data/spec/semipublic/adapters/abstract_adapter_spec.rb +1 -1
  80. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +1 -1
  81. data/spec/semipublic/associations/many_to_many_spec.rb +1 -1
  82. data/spec/semipublic/associations/many_to_one_spec.rb +1 -1
  83. data/spec/semipublic/associations/one_to_many_spec.rb +1 -1
  84. data/spec/semipublic/associations/one_to_one_spec.rb +1 -1
  85. data/spec/semipublic/associations/relationship_spec.rb +1 -1
  86. data/spec/semipublic/associations_spec.rb +1 -1
  87. data/spec/semipublic/collection_spec.rb +1 -1
  88. data/spec/semipublic/model_spec.rb +1 -1
  89. data/spec/semipublic/property/binary_spec.rb +1 -1
  90. data/spec/semipublic/property/boolean_spec.rb +1 -1
  91. data/spec/semipublic/property/class_spec.rb +1 -1
  92. data/spec/semipublic/property/date_spec.rb +1 -1
  93. data/spec/semipublic/property/date_time_spec.rb +1 -1
  94. data/spec/semipublic/property/decimal_spec.rb +6 -5
  95. data/spec/semipublic/property/discriminator_spec.rb +1 -1
  96. data/spec/semipublic/property/float_spec.rb +1 -1
  97. data/spec/semipublic/property/integer_spec.rb +1 -1
  98. data/spec/semipublic/property/lookup_spec.rb +8 -5
  99. data/spec/semipublic/property/serial_spec.rb +1 -1
  100. data/spec/semipublic/property/string_spec.rb +1 -1
  101. data/spec/semipublic/property/text_spec.rb +1 -1
  102. data/spec/semipublic/property/time_spec.rb +1 -1
  103. data/spec/semipublic/property_spec.rb +32 -7
  104. data/spec/semipublic/query/conditions/comparison_spec.rb +1 -264
  105. data/spec/semipublic/query/conditions/operation_spec.rb +1 -2
  106. data/spec/semipublic/query/path_spec.rb +27 -1
  107. data/spec/semipublic/query_spec.rb +87 -36
  108. data/spec/semipublic/resource/state/clean_spec.rb +1 -2
  109. data/spec/semipublic/resource/state/deleted_spec.rb +1 -2
  110. data/spec/semipublic/resource/state/dirty_spec.rb +1 -2
  111. data/spec/semipublic/resource/state/immutable_spec.rb +1 -2
  112. data/spec/semipublic/resource/state/transient_spec.rb +7 -2
  113. data/spec/semipublic/resource/state_spec.rb +1 -1
  114. data/spec/semipublic/resource_spec.rb +1 -1
  115. data/spec/unit/array_spec.rb +1 -0
  116. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  117. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  118. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  119. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  120. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  121. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  122. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  123. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  124. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  125. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  126. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  127. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  128. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  129. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  130. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  131. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  132. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  133. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  134. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  135. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  136. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  137. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  138. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  139. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  140. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  141. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  142. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  143. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  144. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  145. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  146. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  147. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  148. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  149. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  150. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  151. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  152. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  153. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  154. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  155. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  156. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  157. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  158. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  159. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  160. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  161. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  162. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  163. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  164. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  165. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  166. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  167. data/spec/unit/hash_spec.rb +2 -1
  168. data/spec/unit/hook_spec.rb +1 -0
  169. data/spec/unit/lazy_array_spec.rb +2 -1
  170. data/spec/unit/module_spec.rb +2 -1
  171. data/spec/unit/object_spec.rb +1 -0
  172. data/spec/unit/try_dup_spec.rb +1 -0
  173. data/tasks/spec.rake +0 -3
  174. metadata +149 -52
  175. data/.gitignore +0 -37
  176. data/lib/dm-core/type.rb +0 -216
  177. data/lib/dm-core/types/boolean.rb +0 -9
  178. data/lib/dm-core/types/decimal.rb +0 -9
  179. data/lib/dm-core/types/discriminator.rb +0 -50
  180. data/lib/dm-core/types/object.rb +0 -25
  181. data/lib/dm-core/types/serial.rb +0 -11
  182. data/lib/dm-core/types/text.rb +0 -11
  183. data/tasks/local_gemfile.rake +0 -16
  184. data/tasks/metrics.rake +0 -37
@@ -916,7 +916,7 @@ share_examples_for 'A public Resource' do
916
916
  end
917
917
 
918
918
  it 'should succesfully save the dependency' do
919
- @user.attributes.should == @user_model.get(*@user.key).attributes
919
+ @user.name.should == @user_model.get(*@user.key).name
920
920
  end
921
921
  end
922
922
 
@@ -11,9 +11,9 @@ share_examples_for 'A semipublic Property' do
11
11
  end
12
12
  end
13
13
 
14
- @model = Blog::Article
15
- @property = @type.new(@model, @name)
16
- DataMapper.finalize
14
+ @model = Blog::Article
15
+ @options ||= {}
16
+ @property = @type.new(@model, @name, @options)
17
17
  end
18
18
 
19
19
  describe '.new' do
@@ -31,7 +31,7 @@ share_examples_for 'A semipublic Property' do
31
31
  end
32
32
 
33
33
  it 'should set the options to the default' do
34
- @property.options.should == @type.options
34
+ @property.options.should == @options.merge(@type.options)
35
35
  end
36
36
  end
37
37
 
@@ -39,7 +39,7 @@ share_examples_for 'A semipublic Property' do
39
39
  [ true, false, :title, [ :title ] ].each do |value|
40
40
  describe "when provided #{(options = { attribute => value }).inspect}" do
41
41
  before :all do
42
- @property = @type.new(@model, @name, options)
42
+ @property = @type.new(@model, @name, @options.merge(options))
43
43
  end
44
44
 
45
45
  it 'should return a Property' do
@@ -55,7 +55,7 @@ share_examples_for 'A semipublic Property' do
55
55
  end
56
56
 
57
57
  it "should set the options to #{options.inspect}" do
58
- @property.options.should == @type.options.merge(options)
58
+ @property.options.should == @type.options.merge(@options.merge(options))
59
59
  end
60
60
  end
61
61
  end
@@ -64,7 +64,7 @@ share_examples_for 'A semipublic Property' do
64
64
  describe "when provided #{(invalid_options = { attribute => value }).inspect}" do
65
65
  it 'should raise an exception' do
66
66
  lambda {
67
- @type.new(@model, @name, invalid_options)
67
+ @type.new(@model, @name, @options.merge(invalid_options))
68
68
  }.should raise_error(ArgumentError, "options[#{attribute.inspect}] must be either true, false, a Symbol or an Array of Symbols")
69
69
  end
70
70
  end
@@ -125,14 +125,14 @@ share_examples_for 'A semipublic Property' do
125
125
 
126
126
  describe 'when provide a nil value when required' do
127
127
  it 'should return false' do
128
- @property = @type.new(@model, @name, :required => true)
128
+ @property = @type.new(@model, @name, @options.merge(:required => true))
129
129
  @property.valid?(nil).should be(false)
130
130
  end
131
131
  end
132
132
 
133
133
  describe 'when provide a nil value when not required' do
134
134
  it 'should return false' do
135
- @property = @type.new(@model, @name, :required => false)
135
+ @property = @type.new(@model, @name, @options.merge(:required => false))
136
136
  @property.valid?(nil).should be(true)
137
137
  end
138
138
  end
@@ -0,0 +1,261 @@
1
+ shared_examples_for 'DataMapper::Query::Conditions::AbstractComparison' do
2
+ before :all do
3
+ module ::Blog
4
+ class Article
5
+ include DataMapper::Resource
6
+
7
+ property :id, Serial
8
+ property :title, String, :required => true
9
+
10
+ belongs_to :parent, self, :required => false
11
+ has n, :children, self, :inverse => :parent
12
+ end
13
+ end
14
+
15
+ DataMapper.finalize
16
+
17
+ @model = Blog::Article
18
+ end
19
+
20
+ before do
21
+ class ::OtherComparison < DataMapper::Query::Conditions::AbstractComparison
22
+ slug :other
23
+ end
24
+ end
25
+
26
+ before do
27
+ @relationship = @model.relationships[:parent]
28
+ end
29
+
30
+ it { subject.class.should respond_to(:new) }
31
+
32
+ describe '.new' do
33
+ subject { @comparison.class.new(@property, @value) }
34
+
35
+ it { should be_kind_of(@comparison.class) }
36
+
37
+ it { subject.subject.should equal(@property) }
38
+
39
+ it { subject.value.should == @value }
40
+ end
41
+
42
+ it { subject.class.should respond_to(:slug) }
43
+
44
+ describe '.slug' do
45
+ describe 'with no arguments' do
46
+ subject { @comparison.class.slug }
47
+
48
+ it { should == @slug }
49
+ end
50
+
51
+ describe 'with an argument' do
52
+ subject { @comparison.class.slug(:other) }
53
+
54
+ it { should == :other }
55
+
56
+ # reset the slug
57
+ after { @comparison.class.slug(@slug) }
58
+ end
59
+ end
60
+
61
+ it { should respond_to(:==) }
62
+
63
+ describe '#==' do
64
+ describe 'when the other AbstractComparison is equal' do
65
+ # artificially modify the object so #== will throw an
66
+ # exception if the equal? branch is not followed when heckling
67
+ before { @comparison.singleton_class.send(:undef_method, :slug) }
68
+
69
+ subject { @comparison == @comparison }
70
+
71
+ it { should be(true) }
72
+ end
73
+
74
+ describe 'when the other AbstractComparison is the same class' do
75
+ subject { @comparison == DataMapper::Query::Conditions::Comparison.new(@slug, @property, @value) }
76
+
77
+ it { should be(true) }
78
+ end
79
+
80
+ describe 'when the other AbstractComparison is a different class' do
81
+ subject { @comparison == DataMapper::Query::Conditions::Comparison.new(:other, @property, @value) }
82
+
83
+ it { should be(false) }
84
+ end
85
+
86
+ describe 'when the other AbstractComparison is the same class, with different property' do
87
+ subject { @comparison == DataMapper::Query::Conditions::Comparison.new(@slug, @other_property, @value) }
88
+
89
+ it { should be(false) }
90
+ end
91
+
92
+ describe 'when the other AbstractComparison is the same class, with different value' do
93
+ subject { @comparison == DataMapper::Query::Conditions::Comparison.new(@slug, @property, @other_value) }
94
+
95
+ it { should be(false) }
96
+ end
97
+ end
98
+
99
+ it { should respond_to(:eql?) }
100
+
101
+ describe '#eql?' do
102
+ describe 'when the other AbstractComparison is equal' do
103
+ # artificially modify the object so #eql? will throw an
104
+ # exception if the equal? branch is not followed when heckling
105
+ before { @comparison.singleton_class.send(:undef_method, :slug) }
106
+
107
+ subject { @comparison.eql?(@comparison) }
108
+
109
+ it { should be(true) }
110
+ end
111
+
112
+ describe 'when the other AbstractComparison is the same class' do
113
+ subject { @comparison.eql?(DataMapper::Query::Conditions::Comparison.new(@slug, @property, @value)) }
114
+
115
+ it { should be(true) }
116
+ end
117
+
118
+ describe 'when the other AbstractComparison is a different class' do
119
+ subject { @comparison.eql?(DataMapper::Query::Conditions::Comparison.new(:other, @property, @value)) }
120
+
121
+ it { should be(false) }
122
+ end
123
+
124
+ describe 'when the other AbstractComparison is the same class, with different property' do
125
+ subject { @comparison.eql?(DataMapper::Query::Conditions::Comparison.new(@slug, @other_property, @value)) }
126
+
127
+ it { should be(false) }
128
+ end
129
+
130
+ describe 'when the other AbstractComparison is the same class, with different value' do
131
+ subject { @comparison.eql?(DataMapper::Query::Conditions::Comparison.new(@slug, @property, @other_value)) }
132
+
133
+ it { should be(false) }
134
+ end
135
+ end
136
+
137
+ it { should respond_to(:hash) }
138
+
139
+ describe '#hash' do
140
+ subject { @comparison.hash }
141
+
142
+ it 'should match the same AbstractComparison with the same property and value' do
143
+ should == DataMapper::Query::Conditions::Comparison.new(@slug, @property, @value).hash
144
+ end
145
+
146
+ it 'should not match the same AbstractComparison with different property' do
147
+ should_not == DataMapper::Query::Conditions::Comparison.new(@slug, @other_property, @value).hash
148
+ end
149
+
150
+ it 'should not match the same AbstractComparison with different value' do
151
+ should_not == DataMapper::Query::Conditions::Comparison.new(@slug, @property, @other_value).hash
152
+ end
153
+
154
+ it 'should not match a different AbstractComparison with the same property and value' do
155
+ should_not == @other.hash
156
+ end
157
+
158
+ it 'should not match a different AbstractComparison with different property' do
159
+ should_not == @other.class.new(@other_property, @value).hash
160
+ end
161
+
162
+ it 'should not match a different AbstractComparison with different value' do
163
+ should_not == @other.class.new(@property, @other_value).hash
164
+ end
165
+ end
166
+
167
+ it { should respond_to(:loaded_value) }
168
+
169
+ describe '#loaded_value' do
170
+ subject { @comparison.loaded_value }
171
+
172
+ it { should == @value }
173
+ end
174
+
175
+ it { should respond_to(:parent) }
176
+
177
+ describe '#parent' do
178
+ subject { @comparison.parent }
179
+
180
+ describe 'should be nil by default' do
181
+ it { should be_nil }
182
+ end
183
+
184
+ describe 'should relate to parent operation' do
185
+ before do
186
+ @operation = DataMapper::Query::Conditions::Operation.new(:and)
187
+ @comparison.parent = @operation
188
+ end
189
+
190
+ it { should be_equal(@operation) }
191
+ end
192
+ end
193
+
194
+ it { should respond_to(:parent=) }
195
+
196
+ describe '#parent=' do
197
+ before do
198
+ @operation = DataMapper::Query::Conditions::Operation.new(:and)
199
+ end
200
+
201
+ subject { @comparison.parent = @operation }
202
+
203
+ it { should equal(@operation) }
204
+
205
+ it 'should change the parent' do
206
+ method(:subject).should change(@comparison, :parent).
207
+ from(nil).
208
+ to(@operation)
209
+ end
210
+ end
211
+
212
+ it { should respond_to(:property?) }
213
+
214
+ describe '#property?' do
215
+ subject { @comparison.property? }
216
+
217
+ it { should be(true) }
218
+ end
219
+
220
+ it { should respond_to(:slug) }
221
+
222
+ describe '#slug' do
223
+ subject { @comparison.slug }
224
+
225
+ it { should == @slug }
226
+ end
227
+
228
+ it { should respond_to(:subject) }
229
+
230
+ describe '#subject' do
231
+ subject { @comparison.subject }
232
+
233
+ it { should be_equal(@property) }
234
+ end
235
+
236
+ it { should respond_to(:valid?) }
237
+
238
+ describe '#valid?' do
239
+ subject { @comparison.valid? }
240
+
241
+ describe 'when the value is valid for the subject' do
242
+ it { should be(true) }
243
+ end
244
+
245
+ describe 'when the value is not valid for the subject' do
246
+ before do
247
+ @comparison = DataMapper::Query::Conditions::Comparison.new(@slug, @property, nil)
248
+ end
249
+
250
+ it { should be(false) }
251
+ end
252
+ end
253
+
254
+ it { should respond_to(:value) }
255
+
256
+ describe '#value' do
257
+ subject { @comparison.value }
258
+
259
+ it { should == @value }
260
+ end
261
+ end
@@ -3,7 +3,7 @@ module DataMapper
3
3
  def deprecate(old_method, new_method)
4
4
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
5
5
  def #{old_method}(*args, &block)
6
- warn "\#{self.class}##{old_method} is deprecated, use \#{self.class}##{new_method} instead (\#{caller[0]})"
6
+ warn "\#{self.class}##{old_method} is deprecated, use \#{self.class}##{new_method} instead (\#{caller.first})"
7
7
  send(#{new_method.inspect}, *args, &block)
8
8
  end
9
9
  RUBY
@@ -1,3 +1,5 @@
1
+ require 'dm-core/support/subject_set'
2
+
1
3
  module DataMapper
2
4
  class DescendantSet
3
5
  include Enumerable
@@ -7,11 +9,9 @@ module DataMapper
7
9
  # @param [#to_ary] descendants
8
10
  # initialize with the descendants
9
11
  #
10
- # @return [undefined]
11
- #
12
12
  # @api private
13
13
  def initialize(descendants = [])
14
- @descendants = descendants.to_ary
14
+ @descendants = SubjectSet.new(descendants)
15
15
  end
16
16
 
17
17
  # Copy a DescendantSet instance
@@ -19,8 +19,6 @@ module DataMapper
19
19
  # @param [DescendantSet] original
20
20
  # the original descendants
21
21
  #
22
- # @return [undefined]
23
- #
24
22
  # @api private
25
23
  def initialize_copy(original)
26
24
  @descendants = @descendants.dup
@@ -80,5 +78,14 @@ module DataMapper
80
78
  @descendants.empty?
81
79
  end
82
80
 
81
+ # Removes all entries and returns self
82
+ #
83
+ # @return [DescendantSet] self
84
+ #
85
+ # @api private
86
+ def clear
87
+ @descendants.clear
88
+ end
89
+
83
90
  end # class DescendantSet
84
91
  end # module DataMapper
@@ -0,0 +1,382 @@
1
+ require 'dm-core/support/equalizer'
2
+
3
+ module DataMapper
4
+
5
+ # An ordered set of things
6
+ #
7
+ # {OrderedSet} implements set behavior and keeps
8
+ # track of the order in which entries were added.
9
+ #
10
+ # {OrderedSet} allows to inject a class that implements
11
+ # {OrderedSet::Cache::API} at construction time, and will
12
+ # use that cache implementation to enforce set semantics
13
+ # and perform internal caching of insertion order.
14
+ #
15
+ # @see OrderedSet::Cache::API
16
+ # @see OrderedSet::Cache
17
+ # @see SubjectSet::NameCache
18
+ #
19
+ # @api private
20
+ class OrderedSet
21
+
22
+ # The default cache used by {OrderedSet}
23
+ #
24
+ # Uses a {Hash} as internal storage and enforces set semantics
25
+ # by calling #eql? and #hash on the set's entries.
26
+ #
27
+ # @api private
28
+ class Cache
29
+
30
+ # The default implementation of the {API} that {OrderedSet} expects from
31
+ # the cache object that it uses to
32
+ #
33
+ # 1. keep track of insertion order
34
+ # 2. enforce set semantics.
35
+ #
36
+ # Classes including {API} must customize the behavior of the cache in 2 ways:
37
+ #
38
+ # They must determine the value to use as cache key and thus set discriminator,
39
+ # by implementing the {#key_for} method. The {#key_for} method accepts an arbitrary
40
+ # object as param and the method is free to return whatever value from that method.
41
+ # Obviously this will most likely be some attribute or value otherwise derived from
42
+ # the object that got passed in.
43
+ #
44
+ # They must determine which objects are valid set entries by overwriting the
45
+ # {#valid?} method. The {#valid?} method accepts an arbitrary object as param and
46
+ # the overwriting method must return either true or false.
47
+ #
48
+ # The motivation behind this is that set semantics cannot always be enforced
49
+ # by calling {#eql?} and {#hash} on the set's entries. For example, two entries
50
+ # might be considered unique wrt the set if their names are the same, but other
51
+ # internal state differs. This is exactly the case for {DataMapper::Property} and
52
+ # {DataMapper::Associations::Relationship} objects.
53
+ #
54
+ # @see DataMapper::SubjectSet::NameCache
55
+ #
56
+ # @api private
57
+ module API
58
+
59
+ # Initialize a new Cache
60
+ #
61
+ # @api private
62
+ def initialize
63
+ @cache = {}
64
+ end
65
+
66
+ # Tests if the given entry qualifies to be added to the cache
67
+ #
68
+ # @param [Object] entry
69
+ # the entry to be checked
70
+ #
71
+ # @return [Boolean]
72
+ # true if the entry qualifies to be added to the cache
73
+ #
74
+ # @api private
75
+ def valid?(entry)
76
+ raise NotImplementedError, "#{self}#valid? must be implemented"
77
+ end
78
+
79
+ # Given an entry, return the key to be used in the cache
80
+ #
81
+ # @param [Object] entry
82
+ # the entry to get the key for
83
+ #
84
+ # @return [Object, nil]
85
+ # a value derived from the entry that is used as key in the cache
86
+ #
87
+ # @api private
88
+ def key_for(entry)
89
+ raise NotImplementedError, "#{self}#key_for must be implemented"
90
+ end
91
+
92
+ # Check if the entry exists in the cache
93
+ #
94
+ # @param [Object] entry
95
+ # the entry to test for
96
+ #
97
+ # @return [Boolean]
98
+ # true if entry is included in the cache
99
+ #
100
+ # @api private
101
+ def include?(entry)
102
+ @cache.has_key?(key_for(entry))
103
+ end
104
+
105
+ # Return the index for the entry in the cache
106
+ #
107
+ # @param [Object] entry
108
+ # the entry to get the index for
109
+ #
110
+ # @return [Integer, nil]
111
+ # the index for the entry, or nil if it does not exist
112
+ #
113
+ # @api private
114
+ def [](entry)
115
+ @cache[key_for(entry)]
116
+ end
117
+
118
+ # Set the index for the entry in the cache
119
+ #
120
+ # @param [Object] entry
121
+ # the entry to set the index for
122
+ # @param [Integer] index
123
+ # the index to assign to the given entry
124
+ #
125
+ # @return [Integer]
126
+ # the given index for the entry
127
+ #
128
+ # @api private
129
+ def []=(entry, index)
130
+ if valid?(entry)
131
+ @cache[key_for(entry)] = index
132
+ end
133
+ end
134
+
135
+ # Delete an entry from the cache
136
+ #
137
+ # @param [Object] entry
138
+ # the entry to delete from the cache
139
+ #
140
+ # @return [API] self
141
+ #
142
+ # @api private
143
+ def delete(entry)
144
+ deleted_index = @cache.delete(key_for(entry))
145
+ if deleted_index
146
+ @cache.each do |key, index|
147
+ @cache[key] -= 1 if index > deleted_index
148
+ end
149
+ end
150
+ deleted_index
151
+ end
152
+
153
+ # Removes all entries and returns self
154
+ #
155
+ # @return [API] self
156
+ #
157
+ # @api private
158
+ def clear
159
+ @cache.clear
160
+ self
161
+ end
162
+
163
+ end # module API
164
+
165
+ include API
166
+
167
+ # Tests if the given entry qualifies to be added to the cache
168
+ #
169
+ # @param [Object] entry
170
+ # the entry to be checked
171
+ #
172
+ # @return [true] true
173
+ #
174
+ # @api private
175
+ def valid?(entry)
176
+ true
177
+ end
178
+
179
+ # Given an entry, return the key to be used in the cache
180
+ #
181
+ # @param [Object] entry
182
+ # the entry to get the key for
183
+ #
184
+ # @return [Object]
185
+ # the passed in entry
186
+ #
187
+ # @api private
188
+ def key_for(entry)
189
+ entry
190
+ end
191
+
192
+ end # class Cache
193
+
194
+ include Enumerable
195
+ extend Equalizer
196
+
197
+ # This set's entries
198
+ #
199
+ # The order in this Array is not guaranteed
200
+ # to be the order in which the entries were
201
+ # inserted. Use #each to access the entries
202
+ # in insertion order.
203
+ #
204
+ # @return [Array]
205
+ # this set's entries
206
+ #
207
+ # @api private
208
+ attr_reader :entries
209
+
210
+ equalize :entries
211
+
212
+ # Initialize an OrderedSet
213
+ #
214
+ # @param [#each] entries
215
+ # the entries to initialize this set with
216
+ # @param [Class<Cache::API>] cache
217
+ # the cache implementation to use
218
+ #
219
+ # @api private
220
+ def initialize(entries = [], cache = Cache)
221
+ @cache = cache.new
222
+ @entries = []
223
+ merge(entries.to_ary)
224
+ end
225
+
226
+ # Initialize a copy of OrderedSet
227
+ #
228
+ # @api private
229
+ def initialize_copy(*)
230
+ @cache = @cache.dup
231
+ @entries = @entries.dup
232
+ end
233
+
234
+ # Get the entry at the given index
235
+ #
236
+ # @param [Integer] index
237
+ # the index of the desired entry
238
+ #
239
+ # @return [Object, nil]
240
+ # the entry at the given index, or nil if no entry is present
241
+ #
242
+ # @api private
243
+ def [](index)
244
+ entries[index]
245
+ end
246
+
247
+ # Add or update an entry in the set
248
+ #
249
+ # If the entry to add isn't part of the set already,
250
+ # it will be added. If an entry with the same cache
251
+ # key as the entry to add is part of the set already,
252
+ # it will be replaced with the given entry.
253
+ #
254
+ # @param [Object] entry
255
+ # the entry to be added
256
+ #
257
+ # @return [OrderedSet] self
258
+ #
259
+ # @api private
260
+ def <<(entry)
261
+ if index = @cache[entry]
262
+ entries[index] = entry
263
+ else
264
+ @cache[entry] = size
265
+ entries << entry
266
+ end
267
+ self
268
+ end
269
+
270
+ # Merge with another Enumerable object
271
+ #
272
+ # @param [#each] other
273
+ # the Enumerable to merge with this OrderedSet
274
+ #
275
+ # @return [OrderedSet] self
276
+ #
277
+ # @api private
278
+ def merge(other)
279
+ other.each { |entry| self << entry }
280
+ self
281
+ end
282
+
283
+ # Delete an entry from this OrderedSet
284
+ #
285
+ # @param [Object] entry
286
+ # the entry to delete
287
+ #
288
+ # @return [Object, nil]
289
+ # the deleted entry or nil
290
+ #
291
+ # @api private
292
+ def delete(entry)
293
+ if index = @cache.delete(entry)
294
+ entries.delete_at(index)
295
+ end
296
+ end
297
+
298
+ # Removes all entries and returns self
299
+ #
300
+ # @return [OrderedSet] self
301
+ #
302
+ # @api private
303
+ def clear
304
+ @cache.clear
305
+ entries.clear
306
+ self
307
+ end
308
+
309
+ # Iterate over each entry in the set
310
+ #
311
+ # @yield [entry]
312
+ # all entries in the set
313
+ #
314
+ # @yieldparam [Object] entry
315
+ # an entry in the set
316
+ #
317
+ # @return [OrderedSet] self
318
+ #
319
+ # @api private
320
+ def each
321
+ entries.each { |entry| yield(entry) }
322
+ self
323
+ end
324
+
325
+ # The number of entries
326
+ #
327
+ # @return [Integer]
328
+ # the number of entries
329
+ #
330
+ # @api private
331
+ def size
332
+ entries.size
333
+ end
334
+
335
+ # Check if there are any entries
336
+ #
337
+ # @return [Boolean]
338
+ # true if the set is empty
339
+ #
340
+ # @api private
341
+ def empty?
342
+ entries.empty?
343
+ end
344
+
345
+ # Check if the entry exists in the set
346
+ #
347
+ # @param [Object] entry
348
+ # the entry to test for
349
+ #
350
+ # @return [Boolean]
351
+ # true if entry is included in the set
352
+ #
353
+ # @api private
354
+ def include?(entry)
355
+ entries.include?(entry)
356
+ end
357
+
358
+ # Return the index for the entry in the set
359
+ #
360
+ # @param [Object] entry
361
+ # the entry to check the set for
362
+ #
363
+ # @return [Integer, nil]
364
+ # the index for the entry, or nil if it does not exist
365
+ #
366
+ # @api private
367
+ def index(entry)
368
+ @cache[entry]
369
+ end
370
+
371
+ # Convert the OrderedSet into an Array
372
+ #
373
+ # @return [Array]
374
+ # an array containing all the OrderedSet's entries
375
+ #
376
+ # @api private
377
+ def to_ary
378
+ entries
379
+ end
380
+
381
+ end # class OrderedSet
382
+ end # module DataMapper