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
@@ -19,9 +19,7 @@ module DataMapper
19
19
  #
20
20
  # @api private
21
21
  def typecast_to_primitive(value)
22
- if value.respond_to?(:to_datetime)
23
- value.to_datetime
24
- elsif value.respond_to?(:to_mash)
22
+ if value.respond_to?(:to_mash)
25
23
  typecast_hash_to_datetime(value)
26
24
  else
27
25
  ::DateTime.parse(value.to_s)
@@ -7,17 +7,21 @@ module DataMapper
7
7
 
8
8
  protected
9
9
 
10
- def initialize(model, name, options = {}, type = nil)
10
+ def initialize(model, name, options = {})
11
11
  super
12
12
 
13
- unless @scale.nil?
14
- unless @scale >= 0
15
- raise ArgumentError, "scale must be equal to or greater than 0, but was #{@scale.inspect}"
13
+ [ :scale, :precision ].each do |key|
14
+ unless options.key?(key)
15
+ warn "options[#{key.inspect}] should be set for #{self.class}, defaulting to #{send(key).inspect}"
16
16
  end
17
+ end
17
18
 
18
- unless @precision >= @scale
19
- raise ArgumentError, "precision must be equal to or greater than scale, but was #{@precision.inspect} and scale was #{scale_inspect}"
20
- end
19
+ unless @scale >= 0
20
+ raise ArgumentError, "scale must be equal to or greater than 0, but was #{@scale.inspect}"
21
+ end
22
+
23
+ unless @precision >= @scale
24
+ raise ArgumentError, "precision must be equal to or greater than scale, but was #{@precision.inspect} and scale was #{@scale.inspect}"
21
25
  end
22
26
  end
23
27
 
@@ -8,9 +8,9 @@ module DataMapper
8
8
  protected
9
9
 
10
10
  # @api semipublic
11
- def initialize(model, name, options = {}, type = nil)
11
+ def initialize(model, name, options = {})
12
12
  if options.key?(:serial) && !kind_of?(Serial)
13
- warn "Integer #{name} with explicit :serial option is deprecated, use Serial instead (#{caller[2]})"
13
+ raise "Integer #{name} with explicit :serial option is deprecated, use Serial instead (#{caller[2]})"
14
14
  end
15
15
  super
16
16
  end
@@ -6,12 +6,12 @@ module DataMapper
6
6
 
7
7
  #
8
8
  # Provides transparent access to the Properties defined in
9
- # {Property}. It also provides access to the legacy {Types} namespace.
9
+ # {Property}.
10
10
  #
11
11
  # @param [Symbol] name
12
12
  # The name of the property to lookup.
13
13
  #
14
- # @return [Property, Type]
14
+ # @return [Property]
15
15
  # The property with the given name.
16
16
  #
17
17
  # @raise [NameError]
@@ -22,20 +22,7 @@ module DataMapper
22
22
  # @since 1.0.1
23
23
  #
24
24
  def const_missing(name)
25
- if const = Property.find_class(name)
26
- return const
27
- end
28
-
29
- # only check within DataMapper::Types, if it was loaded.
30
- if DataMapper.const_defined?(:Types)
31
- if DataMapper::Types.const_defined?(name)
32
- type = DataMapper::Types.const_get(name)
33
-
34
- return type if type < DataMapper::Type
35
- end
36
- end
37
-
38
- super
25
+ Property.find_class(name.to_s) || super
39
26
  end
40
27
  end
41
28
  end
@@ -15,10 +15,10 @@ module DataMapper
15
15
 
16
16
  protected
17
17
 
18
- def initialize(model, name, options = {}, type = nil)
18
+ def initialize(model, name, options = {})
19
19
  super
20
20
 
21
- if [ BigDecimal, ::Float ].include?(@primitive)
21
+ if @primitive == BigDecimal || @primitive == ::Float
22
22
  @precision = @options.fetch(:precision, DEFAULT_PRECISION)
23
23
  @scale = @options.fetch(:scale, self.class::DEFAULT_SCALE)
24
24
 
@@ -27,7 +27,7 @@ module DataMapper
27
27
  end
28
28
  end
29
29
 
30
- if (@options.keys & [ :min, :max ]).any?
30
+ if @options.key?(:min) || @options.key?(:max)
31
31
  @min = @options.fetch(:min, DEFAULT_NUMERIC_MIN)
32
32
  @max = @options.fetch(:max, DEFAULT_NUMERIC_MAX)
33
33
 
@@ -5,21 +5,12 @@ module DataMapper
5
5
 
6
6
  # @api semipublic
7
7
  def dump(value)
8
- return value if value.nil?
9
-
10
- if @type
11
- @type.dump(value, self)
12
- else
13
- [ Marshal.dump(value) ].pack('m')
14
- end
8
+ return if value.nil?
9
+ [ Marshal.dump(value) ].pack('m')
15
10
  end
16
11
 
17
12
  # @api semipublic
18
13
  def load(value)
19
- if @type
20
- return @type.load(value, self)
21
- end
22
-
23
14
  case value
24
15
  when ::String
25
16
  Marshal.load(value.unpack('m').first)
@@ -27,7 +27,7 @@ module DataMapper
27
27
 
28
28
  protected
29
29
 
30
- def initialize(model, name, options = {}, type = nil)
30
+ def initialize(model, name, options = {})
31
31
  super
32
32
  @length = @options.fetch(:length, DEFAULT_LENGTH)
33
33
  end
@@ -1,58 +1,52 @@
1
+ require 'dm-core/support/subject_set'
2
+
1
3
  module DataMapper
2
4
  # Set of Property objects, used to associate
3
5
  # queries with set of fields it performed over,
4
6
  # to represent composite keys (esp. for associations)
5
7
  # and so on.
6
- class PropertySet < Array
7
- extend Deprecate
8
-
9
- deprecate :has_property?, :named?
10
- deprecate :slice, :values_at
11
- deprecate :add, :<<
8
+ class PropertySet < SubjectSet
9
+ include Enumerable
12
10
 
13
- # @api semipublic
14
- def [](name)
15
- @properties[name]
11
+ def <<(property)
12
+ clear_cache
13
+ super
16
14
  end
17
15
 
18
- alias_method :superclass_slice, :[]=
19
- private :superclass_slice
20
-
16
+ # Make sure that entry is part of this PropertySet
17
+ #
18
+ # @param [#to_s] name
19
+ # @param [#name] entry
20
+ #
21
+ # @return [#name]
22
+ # the entry that is now part of this PropertySet
23
+ #
21
24
  # @api semipublic
22
- def []=(name, property)
23
- self << property
25
+ def []=(name, entry)
26
+ warn "#{self.class}#[]= is deprecated. Use #{self.class}#<< instead: #{caller.first}"
27
+ raise "#{entry.class} is not added with the correct name" unless name && name.to_s == entry.name.to_s
28
+ self << entry
29
+ entry
24
30
  end
25
31
 
26
- # @api semipublic
27
- def named?(name)
28
- @properties.key?(name.to_sym)
32
+ def |(other)
33
+ self.class.new(to_a | other.to_a)
29
34
  end
30
35
 
31
- # @api semipublic
32
- def values_at(*names)
33
- @properties.values_at(*names)
36
+ def &(other)
37
+ self.class.new(to_a & other.to_a)
34
38
  end
35
39
 
36
- # @api semipublic
37
- def <<(property)
38
- found = named?(property.name)
39
- add_property(property)
40
-
41
- if found
42
- superclass_slice(index(property), property)
43
- else
44
- super
45
- end
40
+ def -(other)
41
+ self.class.new(to_a - other.to_a)
46
42
  end
47
43
 
48
- # @api semipublic
49
- def include?(property)
50
- named?(property.name)
44
+ def +(other)
45
+ self.class.new(to_a + other.to_a)
51
46
  end
52
47
 
53
- # @api semipublic
54
- def index(property)
55
- each_index { |index| break index if at(index).name == property.name }
48
+ def ==(other)
49
+ to_a == other.to_a
56
50
  end
57
51
 
58
52
  # TODO: make PropertySet#reject return a PropertySet instance
@@ -68,7 +62,7 @@ module DataMapper
68
62
 
69
63
  # @api semipublic
70
64
  def discriminator
71
- @discriminator ||= detect { |property| property.kind_of?(Property::Discriminator) || property.type == Types::Discriminator }
65
+ @discriminator ||= detect { |property| property.kind_of?(Property::Discriminator) }
72
66
  end
73
67
 
74
68
  # @api semipublic
@@ -153,25 +147,11 @@ module DataMapper
153
147
  map { |property| [ property.field, property ] }.to_hash
154
148
  end
155
149
 
156
- private
157
-
158
- # @api semipublic
159
- def initialize(*)
160
- super
161
- @properties = map { |property| [ property.name, property ] }.to_mash
150
+ def inspect
151
+ to_a.inspect
162
152
  end
163
153
 
164
- # @api private
165
- def initialize_copy(*)
166
- super
167
- @properties = @properties.dup
168
- end
169
-
170
- # @api private
171
- def add_property(property)
172
- clear_cache
173
- @properties[property.name] = property
174
- end
154
+ private
175
155
 
176
156
  # @api private
177
157
  def clear_cache
@@ -486,10 +486,10 @@ module DataMapper
486
486
  #
487
487
  # @api semipublic
488
488
  def filter_records(records)
489
- records = records.uniq if unique?
490
- records = match_records(records)
491
- records = sort_records(records)
492
- records = limit_records(records)
489
+ records = records.uniq if unique?
490
+ records = match_records(records) if conditions
491
+ records = sort_records(records) if order
492
+ records = limit_records(records) if limit || offset > 0
493
493
  records
494
494
  end
495
495
 
@@ -504,7 +504,6 @@ module DataMapper
504
504
  # @api semipublic
505
505
  def match_records(records)
506
506
  conditions = self.conditions
507
- return records if conditions.nil?
508
507
  records.select { |record| conditions.matches?(record) }
509
508
  end
510
509
 
@@ -530,7 +529,7 @@ module DataMapper
530
529
  # Limits a set of records by the offset and/or limit
531
530
  #
532
531
  # @param [Enumerable] records
533
- # A list of Recrods to sort
532
+ # A list of records to sort
534
533
  #
535
534
  # @return [Enumerable]
536
535
  # The offset & limited records
@@ -788,22 +787,26 @@ module DataMapper
788
787
 
789
788
  model = self.model
790
789
 
791
- fields.each do |field|
792
- inspect = field.inspect
790
+ valid_properties = model.properties
791
+
792
+ model.descendants.each do |descendant|
793
+ valid_properties += descendant.properties
794
+ end
793
795
 
796
+ fields.each do |field|
794
797
  case field
795
798
  when Symbol, String
796
- unless @properties.named?(field)
797
- raise ArgumentError, "+options[:fields]+ entry #{inspect} does not map to a property in #{model}"
799
+ unless valid_properties.named?(field)
800
+ raise ArgumentError, "+options[:fields]+ entry #{field.inspect} does not map to a property in #{model}"
798
801
  end
799
802
 
800
803
  when Property
801
- unless @properties.include?(field)
804
+ unless valid_properties.include?(field)
802
805
  raise ArgumentError, "+options[:field]+ entry #{field.name.inspect} does not map to a property in #{model}"
803
806
  end
804
807
 
805
808
  else
806
- raise ArgumentError, "+options[:fields]+ entry #{inspect} of an unsupported object #{field.class}"
809
+ raise ArgumentError, "+options[:fields]+ entry #{field.inspect} of an unsupported object #{field.class}"
807
810
  end
808
811
  end
809
812
  end
@@ -820,12 +823,10 @@ module DataMapper
820
823
  end
821
824
 
822
825
  links.each do |link|
823
- inspect = link.inspect
824
-
825
826
  case link
826
827
  when Symbol, String
827
- unless @relationships.key?(link.to_sym)
828
- raise ArgumentError, "+options[:links]+ entry #{inspect} does not map to a relationship in #{model}"
828
+ unless @relationships.named?(link.to_sym)
829
+ raise ArgumentError, "+options[:links]+ entry #{link.inspect} does not map to a relationship in #{model}"
829
830
  end
830
831
 
831
832
  when Associations::Relationship
@@ -835,7 +836,7 @@ module DataMapper
835
836
  #end
836
837
 
837
838
  else
838
- raise ArgumentError, "+options[:links]+ entry #{inspect} of an unsupported object #{link.class}"
839
+ raise ArgumentError, "+options[:links]+ entry #{link.inspect} of an unsupported object #{link.class}"
839
840
  end
840
841
  end
841
842
  end
@@ -850,19 +851,29 @@ module DataMapper
850
851
  case conditions
851
852
  when Hash
852
853
  conditions.each do |subject, bind_value|
853
- inspect = subject.inspect
854
-
855
854
  case subject
856
855
  when Symbol, ::String
857
- unless subject.to_s.include?('.') || @properties.named?(subject) || @relationships.key?(subject)
858
- raise ArgumentError, "condition #{inspect} does not map to a property or relationship in #{model}"
856
+ original = subject
857
+ subject = subject.to_s
858
+
859
+ if subject.include?('.')
860
+ unless @relationships.named?(subject[0, subject.index('.')])
861
+ raise ArgumentError, "condition #{original.inspect} does not map to a relationship in #{model}"
862
+ end
863
+ elsif !@properties.named?(subject) && !@relationships.named?(subject)
864
+ raise ArgumentError, "condition #{original.inspect} does not map to a property or relationship in #{model}"
865
+ end
866
+
867
+ when Property
868
+ unless @properties.include?(subject)
869
+ raise ArgumentError, "condition #{subject.name.inspect} does not map to a property in #{model}, but belongs to #{subject.model}"
859
870
  end
860
871
 
861
872
  when Operator
862
873
  operator = subject.operator
863
874
 
864
- unless (Conditions::Comparison.slugs | [ :not ]).include?(operator)
865
- raise ArgumentError, "condition #{inspect} used an invalid operator #{operator}"
875
+ unless Conditions::Comparison.slugs.include?(operator) || operator == :not
876
+ raise ArgumentError, "condition #{subject.inspect} used an invalid operator #{operator}"
866
877
  end
867
878
 
868
879
  assert_valid_conditions(subject.target => bind_value)
@@ -870,15 +881,14 @@ module DataMapper
870
881
  when Path
871
882
  assert_valid_links(subject.relationships)
872
883
 
873
- when Associations::Relationship, Property
874
- # TODO: validate that it belongs to the current model, or to any
875
- # model in the links
876
- #unless @properties.include?(subject)
877
- # raise ArgumentError, "condition #{subject.name.inspect} does not map to a property in #{model}"
884
+ when Associations::Relationship
885
+ # TODO: validate that it belongs to the current model
886
+ #unless subject.source_model.equal?(model)
887
+ # raise ArgumentError, "condition #{subject.name.inspect} is not a valid relationship for #{model}, it's source model was #{subject.source_model}"
878
888
  #end
879
889
 
880
890
  else
881
- raise ArgumentError, "condition #{inspect} of an unsupported object #{subject.class}"
891
+ raise ArgumentError, "condition #{subject.inspect} of an unsupported object #{subject.class}"
882
892
  end
883
893
  end
884
894
 
@@ -938,30 +948,27 @@ module DataMapper
938
948
  model = self.model
939
949
 
940
950
  order.each do |order_entry|
941
- inspect = order_entry.inspect
942
-
943
951
  case order_entry
944
952
  when Symbol, String
945
953
  unless @properties.named?(order_entry)
946
- raise ArgumentError, "+options[:order]+ entry #{inspect} does not map to a property in #{model}"
954
+ raise ArgumentError, "+options[:order]+ entry #{order_entry.inspect} does not map to a property in #{model}"
947
955
  end
948
956
 
949
- when Property
950
- unless @properties.include?(order_entry)
951
- raise ArgumentError, "+options[:order]+ entry #{order_entry.name.inspect} does not map to a property in #{model}"
952
- end
957
+ when Property, Path
958
+ # Allow any arbitrary property, since it may map to a model
959
+ # that has been included via the :links option
953
960
 
954
961
  when Operator, Direction
955
962
  operator = order_entry.operator
956
963
 
957
964
  unless operator == :asc || operator == :desc
958
- raise ArgumentError, "+options[:order]+ entry #{inspect} used an invalid operator #{operator}"
965
+ raise ArgumentError, "+options[:order]+ entry #{order_entry.inspect} used an invalid operator #{operator}"
959
966
  end
960
967
 
961
968
  assert_valid_order([ order_entry.target ], fields)
962
969
 
963
970
  else
964
- raise ArgumentError, "+options[:order]+ entry #{inspect} of an unsupported object #{order_entry.class}"
971
+ raise ArgumentError, "+options[:order]+ entry #{order_entry.inspect} of an unsupported object #{order_entry.class}"
965
972
  end
966
973
  end
967
974
  end
@@ -1034,9 +1041,10 @@ module DataMapper
1034
1041
  #
1035
1042
  # @api private
1036
1043
  def normalize_options(options = OPTIONS)
1037
- (options & [ :order, :fields, :links, :unique ]).each do |option|
1038
- send("normalize_#{option}")
1039
- end
1044
+ normalize_order if options.include? :order
1045
+ normalize_fields if options.include? :fields
1046
+ normalize_links if options.include? :links
1047
+ normalize_unique if options.include? :unique
1040
1048
  end
1041
1049
 
1042
1050
  # Normalize order elements to Query::Direction instances
@@ -1045,8 +1053,6 @@ module DataMapper
1045
1053
  def normalize_order
1046
1054
  return if @order.nil?
1047
1055
 
1048
- # TODO: should Query::Path objects be permitted? If so, then it
1049
- # should probably be normalized to a Direction object
1050
1056
  @order = Array(@order)
1051
1057
  @order = @order.map do |order|
1052
1058
  case order
@@ -1064,6 +1070,10 @@ module DataMapper
1064
1070
 
1065
1071
  when Direction
1066
1072
  order.dup
1073
+
1074
+ when Path
1075
+ Direction.new(order.property)
1076
+
1067
1077
  end
1068
1078
  end
1069
1079
  end
@@ -1113,12 +1123,19 @@ module DataMapper
1113
1123
 
1114
1124
  # Normalize the unique attribute
1115
1125
  #
1116
- # If any links are present, and the unique attribute was not
1117
- # explicitly specified, then make sure the query is marked as unique
1126
+ # If any links are present, and the unique attribute was not explicitly
1127
+ # specified, then make sure the query is marked as unique. The exception
1128
+ # to this rule is links where there is known to be 0..1 records on the
1129
+ # the other side, such as belongs_to or a has 1. In this case, there
1130
+ # is no need to explicitly mark the query unique. This in turn allows
1131
+ # sane ordering on fields of the links, since no redundant GROUP BY
1132
+ # clause will be generated.
1118
1133
  #
1119
1134
  # @api private
1120
1135
  def normalize_unique
1121
- @unique = @links.any? unless @options.key?(:unique)
1136
+ unless @options.key?(:unique)
1137
+ @unique = @links.reject {|x| x.min.between?(0, 1) && x.max == 1 }.any?
1138
+ end
1122
1139
  end
1123
1140
 
1124
1141
  # Append conditions to this Query
@@ -1148,6 +1165,16 @@ module DataMapper
1148
1165
  end
1149
1166
  end
1150
1167
 
1168
+ # @api private
1169
+ def equality_operator_for_type(bind_value)
1170
+ case bind_value
1171
+ when Model, String then :eql
1172
+ when Enumerable then :in
1173
+ when Regexp then :regexp
1174
+ else :eql
1175
+ end
1176
+ end
1177
+
1151
1178
  # @api private
1152
1179
  def append_property_condition(subject, bind_value, operator)
1153
1180
  negated = operator == :not
@@ -1171,15 +1198,6 @@ module DataMapper
1171
1198
  add_condition(condition)
1172
1199
  end
1173
1200
 
1174
- def equality_operator_for_type(bind_value)
1175
- case bind_value
1176
- when Model, String then :eql
1177
- when Enumerable then :in
1178
- when Regexp then :regexp
1179
- else :eql
1180
- end
1181
- end
1182
-
1183
1201
  # @api private
1184
1202
  def append_symbol_condition(symbol, bind_value, model, operator)
1185
1203
  append_condition(symbol.to_s, bind_value, model, operator)
@@ -1356,7 +1374,18 @@ module DataMapper
1356
1374
  #
1357
1375
  # @api private
1358
1376
  def other_conditions(other, operation)
1359
- query_conditions(self).send(operation, query_conditions(other))
1377
+ self_conditions = query_conditions(self)
1378
+
1379
+ unless self_conditions.kind_of?(Conditions::Operation)
1380
+ operation_slug = case operation
1381
+ when :intersection, :difference then :and
1382
+ when :union then :or
1383
+ end
1384
+
1385
+ self_conditions = Conditions::Operation.new(operation_slug, self_conditions)
1386
+ end
1387
+
1388
+ self_conditions.send(operation, query_conditions(other))
1360
1389
  end
1361
1390
 
1362
1391
  # Extract conditions from a Query