dm-core 0.9.11 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. data/.autotest +17 -14
  2. data/.gitignore +3 -1
  3. data/FAQ +6 -5
  4. data/History.txt +5 -50
  5. data/Manifest.txt +66 -76
  6. data/QUICKLINKS +1 -1
  7. data/README.txt +21 -15
  8. data/Rakefile +6 -7
  9. data/SPECS +2 -29
  10. data/TODO +1 -1
  11. data/deps.rip +2 -0
  12. data/dm-core.gemspec +11 -15
  13. data/lib/dm-core.rb +105 -110
  14. data/lib/dm-core/adapters.rb +135 -16
  15. data/lib/dm-core/adapters/abstract_adapter.rb +251 -181
  16. data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
  17. data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
  18. data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
  19. data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
  20. data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
  21. data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
  22. data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
  23. data/lib/dm-core/associations/many_to_many.rb +372 -90
  24. data/lib/dm-core/associations/many_to_one.rb +220 -73
  25. data/lib/dm-core/associations/one_to_many.rb +319 -255
  26. data/lib/dm-core/associations/one_to_one.rb +66 -53
  27. data/lib/dm-core/associations/relationship.rb +561 -156
  28. data/lib/dm-core/collection.rb +1101 -379
  29. data/lib/dm-core/core_ext/kernel.rb +12 -0
  30. data/lib/dm-core/core_ext/symbol.rb +10 -0
  31. data/lib/dm-core/identity_map.rb +4 -34
  32. data/lib/dm-core/migrations.rb +1283 -0
  33. data/lib/dm-core/model.rb +570 -369
  34. data/lib/dm-core/model/descendant_set.rb +81 -0
  35. data/lib/dm-core/model/hook.rb +45 -0
  36. data/lib/dm-core/model/is.rb +32 -0
  37. data/lib/dm-core/model/property.rb +247 -0
  38. data/lib/dm-core/model/relationship.rb +335 -0
  39. data/lib/dm-core/model/scope.rb +90 -0
  40. data/lib/dm-core/property.rb +808 -273
  41. data/lib/dm-core/property_set.rb +141 -98
  42. data/lib/dm-core/query.rb +1037 -483
  43. data/lib/dm-core/query/conditions/comparison.rb +872 -0
  44. data/lib/dm-core/query/conditions/operation.rb +221 -0
  45. data/lib/dm-core/query/direction.rb +43 -0
  46. data/lib/dm-core/query/operator.rb +84 -0
  47. data/lib/dm-core/query/path.rb +138 -0
  48. data/lib/dm-core/query/sort.rb +45 -0
  49. data/lib/dm-core/repository.rb +210 -94
  50. data/lib/dm-core/resource.rb +641 -421
  51. data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
  52. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
  53. data/lib/dm-core/support/chainable.rb +22 -0
  54. data/lib/dm-core/support/deprecate.rb +12 -0
  55. data/lib/dm-core/support/logger.rb +13 -0
  56. data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
  57. data/lib/dm-core/transaction.rb +333 -92
  58. data/lib/dm-core/type.rb +98 -60
  59. data/lib/dm-core/types/boolean.rb +1 -1
  60. data/lib/dm-core/types/discriminator.rb +34 -20
  61. data/lib/dm-core/types/object.rb +7 -4
  62. data/lib/dm-core/types/paranoid_boolean.rb +11 -9
  63. data/lib/dm-core/types/paranoid_datetime.rb +11 -9
  64. data/lib/dm-core/types/serial.rb +3 -3
  65. data/lib/dm-core/types/text.rb +3 -4
  66. data/lib/dm-core/version.rb +1 -1
  67. data/script/performance.rb +102 -109
  68. data/script/profile.rb +169 -38
  69. data/spec/lib/adapter_helpers.rb +105 -0
  70. data/spec/lib/collection_helpers.rb +18 -0
  71. data/spec/lib/counter_adapter.rb +34 -0
  72. data/spec/lib/pending_helpers.rb +27 -0
  73. data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
  74. data/spec/public/associations/many_to_many_spec.rb +193 -0
  75. data/spec/public/associations/many_to_one_spec.rb +73 -0
  76. data/spec/public/associations/one_to_many_spec.rb +77 -0
  77. data/spec/public/associations/one_to_one_spec.rb +156 -0
  78. data/spec/public/collection_spec.rb +65 -0
  79. data/spec/public/migrations_spec.rb +359 -0
  80. data/spec/public/model/relationship_spec.rb +924 -0
  81. data/spec/public/model_spec.rb +159 -0
  82. data/spec/public/property_spec.rb +829 -0
  83. data/spec/public/resource_spec.rb +71 -0
  84. data/spec/public/sel_spec.rb +44 -0
  85. data/spec/public/setup_spec.rb +145 -0
  86. data/spec/public/shared/association_collection_shared_spec.rb +317 -0
  87. data/spec/public/shared/collection_shared_spec.rb +1670 -0
  88. data/spec/public/shared/finder_shared_spec.rb +1619 -0
  89. data/spec/public/shared/resource_shared_spec.rb +924 -0
  90. data/spec/public/shared/sel_shared_spec.rb +112 -0
  91. data/spec/public/transaction_spec.rb +129 -0
  92. data/spec/public/types/discriminator_spec.rb +130 -0
  93. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  94. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  95. data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
  96. data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
  97. data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
  98. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
  99. data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
  100. data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
  101. data/spec/semipublic/associations/relationship_spec.rb +194 -0
  102. data/spec/semipublic/associations_spec.rb +177 -0
  103. data/spec/semipublic/collection_spec.rb +142 -0
  104. data/spec/semipublic/property_spec.rb +61 -0
  105. data/spec/semipublic/query/conditions_spec.rb +528 -0
  106. data/spec/semipublic/query/path_spec.rb +443 -0
  107. data/spec/semipublic/query_spec.rb +2626 -0
  108. data/spec/semipublic/resource_spec.rb +47 -0
  109. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  110. data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
  111. data/spec/spec.opts +3 -1
  112. data/spec/spec_helper.rb +80 -57
  113. data/tasks/ci.rb +19 -31
  114. data/tasks/dm.rb +43 -48
  115. data/tasks/doc.rb +8 -11
  116. data/tasks/gemspec.rb +5 -5
  117. data/tasks/hoe.rb +15 -16
  118. data/tasks/install.rb +8 -10
  119. metadata +74 -111
  120. data/lib/dm-core/associations.rb +0 -207
  121. data/lib/dm-core/associations/relationship_chain.rb +0 -81
  122. data/lib/dm-core/auto_migrations.rb +0 -105
  123. data/lib/dm-core/dependency_queue.rb +0 -32
  124. data/lib/dm-core/hook.rb +0 -11
  125. data/lib/dm-core/is.rb +0 -16
  126. data/lib/dm-core/logger.rb +0 -232
  127. data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
  128. data/lib/dm-core/migrator.rb +0 -29
  129. data/lib/dm-core/scope.rb +0 -58
  130. data/lib/dm-core/support.rb +0 -7
  131. data/lib/dm-core/support/array.rb +0 -13
  132. data/lib/dm-core/support/assertions.rb +0 -8
  133. data/lib/dm-core/support/errors.rb +0 -23
  134. data/lib/dm-core/support/kernel.rb +0 -11
  135. data/lib/dm-core/support/symbol.rb +0 -41
  136. data/lib/dm-core/type_map.rb +0 -80
  137. data/lib/dm-core/types.rb +0 -19
  138. data/script/all +0 -4
  139. data/spec/integration/association_spec.rb +0 -1382
  140. data/spec/integration/association_through_spec.rb +0 -203
  141. data/spec/integration/associations/many_to_many_spec.rb +0 -449
  142. data/spec/integration/associations/many_to_one_spec.rb +0 -163
  143. data/spec/integration/associations/one_to_many_spec.rb +0 -188
  144. data/spec/integration/auto_migrations_spec.rb +0 -413
  145. data/spec/integration/collection_spec.rb +0 -1073
  146. data/spec/integration/data_objects_adapter_spec.rb +0 -32
  147. data/spec/integration/dependency_queue_spec.rb +0 -46
  148. data/spec/integration/model_spec.rb +0 -197
  149. data/spec/integration/mysql_adapter_spec.rb +0 -85
  150. data/spec/integration/postgres_adapter_spec.rb +0 -731
  151. data/spec/integration/property_spec.rb +0 -253
  152. data/spec/integration/query_spec.rb +0 -514
  153. data/spec/integration/repository_spec.rb +0 -61
  154. data/spec/integration/resource_spec.rb +0 -513
  155. data/spec/integration/sqlite3_adapter_spec.rb +0 -352
  156. data/spec/integration/sti_spec.rb +0 -273
  157. data/spec/integration/strategic_eager_loading_spec.rb +0 -156
  158. data/spec/integration/transaction_spec.rb +0 -75
  159. data/spec/integration/type_spec.rb +0 -275
  160. data/spec/lib/logging_helper.rb +0 -18
  161. data/spec/lib/mock_adapter.rb +0 -27
  162. data/spec/lib/model_loader.rb +0 -100
  163. data/spec/lib/publicize_methods.rb +0 -28
  164. data/spec/models/content.rb +0 -16
  165. data/spec/models/vehicles.rb +0 -34
  166. data/spec/models/zoo.rb +0 -48
  167. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
  168. data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
  169. data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
  170. data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
  171. data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
  172. data/spec/unit/associations/many_to_many_spec.rb +0 -32
  173. data/spec/unit/associations/many_to_one_spec.rb +0 -159
  174. data/spec/unit/associations/one_to_many_spec.rb +0 -393
  175. data/spec/unit/associations/one_to_one_spec.rb +0 -7
  176. data/spec/unit/associations/relationship_spec.rb +0 -71
  177. data/spec/unit/associations_spec.rb +0 -242
  178. data/spec/unit/auto_migrations_spec.rb +0 -111
  179. data/spec/unit/collection_spec.rb +0 -182
  180. data/spec/unit/data_mapper_spec.rb +0 -35
  181. data/spec/unit/identity_map_spec.rb +0 -126
  182. data/spec/unit/is_spec.rb +0 -80
  183. data/spec/unit/migrator_spec.rb +0 -33
  184. data/spec/unit/model_spec.rb +0 -321
  185. data/spec/unit/naming_conventions_spec.rb +0 -36
  186. data/spec/unit/property_set_spec.rb +0 -90
  187. data/spec/unit/property_spec.rb +0 -753
  188. data/spec/unit/query_spec.rb +0 -571
  189. data/spec/unit/repository_spec.rb +0 -93
  190. data/spec/unit/resource_spec.rb +0 -649
  191. data/spec/unit/scope_spec.rb +0 -142
  192. data/spec/unit/transaction_spec.rb +0 -493
  193. data/spec/unit/type_map_spec.rb +0 -114
  194. data/spec/unit/type_spec.rb +0 -119
@@ -0,0 +1,221 @@
1
+ module DataMapper
2
+ class Query
3
+ module Conditions
4
+ class InvalidOperation < ArgumentError; end
5
+
6
+ class Operation
7
+ # TODO: document
8
+ # @api semipublic
9
+ def self.new(slug, *operands)
10
+ if klass = operation_class(slug)
11
+ klass.new(*operands)
12
+ else
13
+ raise "No Operation class for `#{slug.inspect}' has been defined"
14
+ end
15
+ end
16
+
17
+ # TODO: document
18
+ # @api semipublic
19
+ def self.operation_class(slug)
20
+ operation_classes[slug] ||= AbstractOperation.descendants.detect { |operation_class| operation_class.slug == slug }
21
+ end
22
+
23
+ # TODO: document
24
+ # @api private
25
+ def self.slugs
26
+ @slugs ||= AbstractOperation.descendants.map { |operation_class| operation_class.slug }
27
+ end
28
+
29
+ class << self
30
+ private
31
+
32
+ # TODO: document
33
+ # @api private
34
+ def operation_classes
35
+ @operation_classes ||= {}
36
+ end
37
+ end
38
+ end # class Operation
39
+
40
+ class AbstractOperation
41
+ include Enumerable
42
+
43
+ # TODO: document
44
+ # @api semipublic
45
+ attr_reader :operands
46
+
47
+ # TODO: document
48
+ # @api private
49
+ def self.descendants
50
+ @descendants ||= Set.new
51
+ end
52
+
53
+ # TODO: document
54
+ # @api private
55
+ def self.inherited(operation_class)
56
+ descendants << operation_class
57
+ end
58
+
59
+ # TODO: document
60
+ # @api semipublic
61
+ def self.slug(slug = nil)
62
+ slug ? @slug = slug : @slug
63
+ end
64
+
65
+ # TODO: document
66
+ # @api semipublic
67
+ def each
68
+ @operands.each { |*block_args| yield(*block_args) }
69
+ end
70
+
71
+ # TODO: document
72
+ # @api semipublic
73
+ def valid?
74
+ operands.all? do |operand|
75
+ if operand.respond_to?(:valid?)
76
+ operand.valid?
77
+ else
78
+ true
79
+ end
80
+ end
81
+ end
82
+
83
+ # TODO: document
84
+ # @api semipublic
85
+ def <<(operand)
86
+ @operands << operand
87
+ self
88
+ end
89
+
90
+ # TODO: document
91
+ # @api semipublic
92
+ def hash
93
+ @operands.sort_by { |operand| operand.hash }.hash
94
+ end
95
+
96
+ # TODO: document
97
+ # @api semipublic
98
+ def ==(other)
99
+ if equal?(other)
100
+ return true
101
+ end
102
+
103
+ other_class = other.class
104
+
105
+ unless other_class.respond_to?(:slug) && other_class.slug == self.class.slug
106
+ return false
107
+ end
108
+
109
+ unless other.respond_to?(:operands)
110
+ return false
111
+ end
112
+
113
+ cmp?(other, :==)
114
+ end
115
+
116
+ # TODO: document
117
+ # @api semipublic
118
+ def eql?(other)
119
+ if equal?(other)
120
+ return true
121
+ end
122
+
123
+ unless instance_of?(other.class)
124
+ return false
125
+ end
126
+
127
+ cmp?(other, :eql?)
128
+ end
129
+
130
+ # TODO: document
131
+ # @api semipublic
132
+ def inspect
133
+ "#<#{self.class} @operands=#{@operands.inspect}>"
134
+ end
135
+
136
+ private
137
+
138
+ # TODO: document
139
+ # @api semipublic
140
+ def initialize(*operands)
141
+ @operands = operands
142
+ end
143
+
144
+ # TODO: document
145
+ # @api semipublic
146
+ def initialize_copy(*)
147
+ @operands = @operands.map { |operand| operand.dup }
148
+ end
149
+
150
+ # TODO: document
151
+ # @api private
152
+ def cmp?(other, operator)
153
+ @operands.sort_by { |operand| operand.hash }.send(operator, other.operands.sort_by { |operand| operand.hash })
154
+ end
155
+ end # class AbstractOperation
156
+
157
+ module FlattenOperation
158
+ # TODO: document
159
+ # @api semipublic
160
+ def <<(operand)
161
+ if operand.kind_of?(self.class)
162
+ @operands.concat(operand.operands)
163
+ self
164
+ else
165
+ super
166
+ end
167
+ end
168
+ end # module FlattenOperation
169
+
170
+ class AndOperation < AbstractOperation
171
+ include FlattenOperation
172
+
173
+ slug :and
174
+
175
+ # TODO: document
176
+ # @api semipublic
177
+ def matches?(record)
178
+ @operands.all? { |operand| operand.matches?(record) }
179
+ end
180
+ end # class AndOperation
181
+
182
+ class OrOperation < AbstractOperation
183
+ include FlattenOperation
184
+
185
+ slug :or
186
+
187
+ # TODO: document
188
+ # @api semipublic
189
+ def matches?(record)
190
+ @operands.any? { |operand| operand.matches?(record) }
191
+ end
192
+ end # class OrOperation
193
+
194
+ class NotOperation < AbstractOperation
195
+ slug :not
196
+
197
+ # TODO: document
198
+ # @api semipublic
199
+ def matches?(record)
200
+ not @operands.first.matches?(record)
201
+ end
202
+
203
+ # TODO: document
204
+ # @api semipublic
205
+ def <<(operand)
206
+ raise ArgumentError, "#{self.class} cannot have more than one operand" if @operands.size > 0
207
+ super
208
+ end
209
+
210
+ private
211
+
212
+ # TODO: document
213
+ # @api semipublic
214
+ def initialize(*operands)
215
+ raise InvalidOperation, "#{self.class} is a unary operator" if operands.size > 1
216
+ super
217
+ end
218
+ end # class NotOperation
219
+ end # module Conditions
220
+ end # class Query
221
+ end # module DataMapper
@@ -0,0 +1,43 @@
1
+ # TODO: rename this DM::Symbol::Direction
2
+
3
+ # TODO: add a method to convert it into a DM::Query::Sort object, eg:
4
+ # operator.sort_for(model)
5
+
6
+ # TODO: rename #target to #property_name
7
+
8
+ # TODO: make sure Query converts this into a DM::Query::Sort object
9
+ # immediately and passes that down to the Adapter
10
+
11
+ # TODO: remove #get method
12
+
13
+ module DataMapper
14
+ class Query
15
+ class Direction < Operator
16
+ extend Deprecate
17
+
18
+ deprecate :property, :target
19
+ deprecate :direction, :operator
20
+
21
+ # TODO: document
22
+ # @api private
23
+ def reverse!
24
+ @operator = @operator == :asc ? :desc : :asc
25
+ self
26
+ end
27
+
28
+ # TODO: document
29
+ # @api private
30
+ def get(resource)
31
+ Sort.new(target.get(resource), @operator == :asc)
32
+ end
33
+
34
+ private
35
+
36
+ # TODO: document
37
+ # @api private
38
+ def initialize(target, operator = :asc)
39
+ super
40
+ end
41
+ end # class Direction
42
+ end # class Query
43
+ end # module DataMapper
@@ -0,0 +1,84 @@
1
+ # TODO: rename this DM::Symbol::Operator
2
+
3
+ # TODO: add a method to convert it into a DM::Query::AbstractComparison object, eg:
4
+ # operator.comparison_for(repository, model)
5
+
6
+ # TODO: rename #target to #property_name
7
+
8
+ module DataMapper
9
+ class Query
10
+ class Operator
11
+ include Extlib::Assertions
12
+
13
+ # TODO: document
14
+ # @api private
15
+ attr_reader :target
16
+
17
+ # TODO: document
18
+ # @api private
19
+ attr_reader :operator
20
+
21
+ # TODO: document
22
+ # @api private
23
+ def ==(other)
24
+ if equal?(other)
25
+ return true
26
+ end
27
+
28
+ unless other.respond_to?(:target)
29
+ return false
30
+ end
31
+
32
+ unless other.respond_to?(:operator)
33
+ return false
34
+ end
35
+
36
+ cmp?(other, :==)
37
+ end
38
+
39
+ # TODO: document
40
+ # @api private
41
+ def eql?(other)
42
+ if equal?(other)
43
+ return true
44
+ end
45
+
46
+ unless instance_of?(other.class)
47
+ return false
48
+ end
49
+
50
+ cmp?(other, :eql?)
51
+ end
52
+
53
+ # TODO: document
54
+ # @api private
55
+ def hash
56
+ @target.hash
57
+ end
58
+
59
+ # TODO: document
60
+ # @api private
61
+ def inspect
62
+ "#<#{self.class.name} @target=#{target.inspect} @operator=#{operator.inspect}>"
63
+ end
64
+
65
+ private
66
+
67
+ # TODO: document
68
+ # @api private
69
+ def initialize(target, operator)
70
+ assert_kind_of 'operator', operator, Symbol
71
+
72
+ @target = target
73
+ @operator = operator
74
+ end
75
+
76
+ # TODO: document
77
+ # @api private
78
+ def cmp?(other, operator)
79
+ target.send(operator, other.target) &&
80
+ self.operator.send(operator, other.operator)
81
+ end
82
+ end # class Operator
83
+ end # class Query
84
+ end # module DataMapper
@@ -0,0 +1,138 @@
1
+ # TODO: instead of an Array of Path objects, create a Relationship
2
+ # on the fly using :through on the previous relationship, creating a
3
+ # chain. Query::Path could then be a thin wrapper that specifies extra
4
+ # conditions on the Relationships, like the target property o match
5
+ # on.
6
+
7
+ module DataMapper
8
+ class Query
9
+ class Path
10
+ instance_methods.each { |method| undef_method method unless %w[ __id__ __send__ send class dup object_id kind_of? instance_of? respond_to? equal? should should_not instance_variable_set instance_variable_get extend hash inspect ].include?(method.to_s) }
11
+
12
+ include Extlib::Assertions
13
+
14
+ # TODO: document
15
+ # @api semipublic
16
+ attr_reader :repository_name
17
+
18
+ # TODO: document
19
+ # @api semipublic
20
+ attr_reader :relationships
21
+
22
+ # TODO: document
23
+ # @api semipublic
24
+ attr_reader :model
25
+
26
+ # TODO: document
27
+ # @api semipublic
28
+ attr_reader :property
29
+
30
+ (Conditions::Comparison.slugs | [ :not ]).each do |slug|
31
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
32
+ def #{slug} # def eql
33
+ #{"warn \"explicit use of '#{slug}' operator is deprecated (#{caller[0]})\"" if slug == :eql || slug == :in} # warn "explicit use of 'eql' operator is deprecated (#{caller[0]})"
34
+ Operator.new(self, #{slug.inspect}) # Operator.new(self, :eql)
35
+ end # end
36
+ RUBY
37
+ end
38
+
39
+ # TODO: document
40
+ # @api public
41
+ def kind_of?(klass)
42
+ super || (defined?(@property) && @property.kind_of?(klass))
43
+ end
44
+
45
+ # TODO: document
46
+ # @api public
47
+ def instance_of?(klass)
48
+ super || (defined?(@property) && @property.instance_of?(klass))
49
+ end
50
+
51
+ # TODO: document
52
+ # @api semipublic
53
+ def respond_to?(method, include_private = false)
54
+ super ||
55
+ (defined?(@property) && @property.respond_to?(method, include_private)) ||
56
+ @model.relationships(@repository_name).key?(method) ||
57
+ @model.properties(@repository_name).named?(method)
58
+ end
59
+
60
+ # TODO: document
61
+ # @api semipublic
62
+ def ==(other)
63
+ if equal?(other)
64
+ return true
65
+ end
66
+
67
+ unless other.respond_to?(:relationships)
68
+ return false
69
+ end
70
+
71
+ unless other.respond_to?(:property)
72
+ return false
73
+ end
74
+
75
+ cmp?(other, :==)
76
+ end
77
+
78
+ # TODO: document
79
+ # @api semipublic
80
+ def eql?(other)
81
+ if equal?(other)
82
+ return true
83
+ end
84
+
85
+ unless instance_of?(other.class)
86
+ return false
87
+ end
88
+
89
+ cmp?(other, :eql?)
90
+ end
91
+
92
+ private
93
+
94
+ # TODO: document
95
+ # @api semipublic
96
+ def initialize(relationships, property_name = nil)
97
+ assert_kind_of 'relationships', relationships, Array
98
+ assert_kind_of 'property_name', property_name, Symbol, NilClass
99
+
100
+ @relationships = relationships.dup
101
+
102
+ last_relationship = @relationships.last
103
+ @repository_name = last_relationship.relative_target_repository_name
104
+ @model = last_relationship.target_model
105
+
106
+ if property_name
107
+ @property = @model.properties(@repository_name)[property_name] ||
108
+ raise(ArgumentError, "Unknown property '#{property_name}' in #{@model}")
109
+ end
110
+ end
111
+
112
+ # TODO: document
113
+ # @api private
114
+ def cmp?(other, operator)
115
+ relationships.send(operator, other.relationships) &&
116
+ property.send(operator, other.property)
117
+ end
118
+
119
+ # TODO: document
120
+ # @api semipublic
121
+ def method_missing(method, *args)
122
+ if @property
123
+ return @property.send(method, *args)
124
+ end
125
+
126
+ if relationship = @model.relationships(@repository_name)[method]
127
+ return self.class.new(@relationships.dup << relationship)
128
+ end
129
+
130
+ if @model.properties(@repository_name).named?(method)
131
+ return self.class.new(@relationships, method)
132
+ end
133
+
134
+ raise NoMethodError, "undefined property or relationship '#{method}' on #{@model}"
135
+ end
136
+ end # class Path
137
+ end # class Query
138
+ end # module DataMapper