datamapper-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.
- data/.autotest +17 -14
- data/.gitignore +3 -1
- data/FAQ +6 -5
- data/History.txt +5 -39
- data/Manifest.txt +67 -76
- data/QUICKLINKS +1 -1
- data/README.txt +21 -15
- data/Rakefile +16 -15
- data/SPECS +2 -29
- data/TODO +1 -1
- data/dm-core.gemspec +11 -15
- data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
- data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
- data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
- data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
- data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
- data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
- data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
- data/lib/dm-core/adapters.rb +135 -16
- data/lib/dm-core/associations/many_to_many.rb +372 -90
- data/lib/dm-core/associations/many_to_one.rb +220 -73
- data/lib/dm-core/associations/one_to_many.rb +319 -255
- data/lib/dm-core/associations/one_to_one.rb +66 -53
- data/lib/dm-core/associations/relationship.rb +560 -158
- data/lib/dm-core/collection.rb +1104 -381
- data/lib/dm-core/core_ext/kernel.rb +12 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +4 -34
- data/lib/dm-core/migrations.rb +1283 -0
- data/lib/dm-core/model/descendant_set.rb +81 -0
- data/lib/dm-core/model/hook.rb +45 -0
- data/lib/dm-core/model/is.rb +32 -0
- data/lib/dm-core/model/property.rb +248 -0
- data/lib/dm-core/model/relationship.rb +335 -0
- data/lib/dm-core/model/scope.rb +90 -0
- data/lib/dm-core/model.rb +570 -369
- data/lib/dm-core/property.rb +753 -280
- data/lib/dm-core/property_set.rb +141 -98
- data/lib/dm-core/query/conditions/comparison.rb +814 -0
- data/lib/dm-core/query/conditions/operation.rb +247 -0
- data/lib/dm-core/query/direction.rb +43 -0
- data/lib/dm-core/query/operator.rb +42 -0
- data/lib/dm-core/query/path.rb +102 -0
- data/lib/dm-core/query/sort.rb +45 -0
- data/lib/dm-core/query.rb +974 -492
- data/lib/dm-core/repository.rb +147 -107
- data/lib/dm-core/resource.rb +644 -429
- data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
- data/lib/dm-core/support/chainable.rb +20 -0
- data/lib/dm-core/support/deprecate.rb +12 -0
- data/lib/dm-core/support/equalizer.rb +23 -0
- data/lib/dm-core/support/logger.rb +13 -0
- data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
- data/lib/dm-core/transaction.rb +333 -92
- data/lib/dm-core/type.rb +98 -60
- data/lib/dm-core/types/boolean.rb +1 -1
- data/lib/dm-core/types/discriminator.rb +34 -20
- data/lib/dm-core/types/object.rb +7 -4
- data/lib/dm-core/types/paranoid_boolean.rb +11 -9
- data/lib/dm-core/types/paranoid_datetime.rb +11 -9
- data/lib/dm-core/types/serial.rb +3 -3
- data/lib/dm-core/types/text.rb +3 -4
- data/lib/dm-core/version.rb +1 -1
- data/lib/dm-core.rb +106 -110
- data/script/performance.rb +102 -109
- data/script/profile.rb +169 -38
- data/spec/lib/adapter_helpers.rb +105 -0
- data/spec/lib/collection_helpers.rb +18 -0
- data/spec/lib/counter_adapter.rb +34 -0
- data/spec/lib/pending_helpers.rb +27 -0
- data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
- data/spec/public/associations/many_to_many_spec.rb +193 -0
- data/spec/public/associations/many_to_one_spec.rb +73 -0
- data/spec/public/associations/one_to_many_spec.rb +77 -0
- data/spec/public/associations/one_to_one_spec.rb +156 -0
- data/spec/public/collection_spec.rb +65 -0
- data/spec/public/model/relationship_spec.rb +924 -0
- data/spec/public/model_spec.rb +159 -0
- data/spec/public/property_spec.rb +829 -0
- data/spec/public/resource_spec.rb +71 -0
- data/spec/public/sel_spec.rb +44 -0
- data/spec/public/setup_spec.rb +145 -0
- data/spec/public/shared/association_collection_shared_spec.rb +317 -0
- data/spec/public/shared/collection_shared_spec.rb +1723 -0
- data/spec/public/shared/finder_shared_spec.rb +1619 -0
- data/spec/public/shared/resource_shared_spec.rb +924 -0
- data/spec/public/shared/sel_shared_spec.rb +112 -0
- data/spec/public/transaction_spec.rb +129 -0
- data/spec/public/types/discriminator_spec.rb +130 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
- data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
- data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
- data/spec/semipublic/associations/relationship_spec.rb +194 -0
- data/spec/semipublic/associations_spec.rb +177 -0
- data/spec/semipublic/collection_spec.rb +142 -0
- data/spec/semipublic/property_spec.rb +61 -0
- data/spec/semipublic/query/conditions_spec.rb +528 -0
- data/spec/semipublic/query/path_spec.rb +443 -0
- data/spec/semipublic/query_spec.rb +2626 -0
- data/spec/semipublic/resource_spec.rb +47 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
- data/spec/spec.opts +3 -1
- data/spec/spec_helper.rb +80 -57
- data/tasks/ci.rb +19 -31
- data/tasks/dm.rb +43 -48
- data/tasks/doc.rb +8 -11
- data/tasks/gemspec.rb +5 -5
- data/tasks/hoe.rb +15 -16
- data/tasks/install.rb +8 -10
- metadata +72 -93
- data/lib/dm-core/associations/relationship_chain.rb +0 -81
- data/lib/dm-core/associations.rb +0 -207
- data/lib/dm-core/auto_migrations.rb +0 -105
- data/lib/dm-core/dependency_queue.rb +0 -32
- data/lib/dm-core/hook.rb +0 -11
- data/lib/dm-core/is.rb +0 -16
- data/lib/dm-core/logger.rb +0 -232
- data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
- data/lib/dm-core/migrator.rb +0 -29
- data/lib/dm-core/scope.rb +0 -58
- data/lib/dm-core/support/array.rb +0 -13
- data/lib/dm-core/support/assertions.rb +0 -8
- data/lib/dm-core/support/errors.rb +0 -23
- data/lib/dm-core/support/kernel.rb +0 -11
- data/lib/dm-core/support/symbol.rb +0 -41
- data/lib/dm-core/support.rb +0 -7
- data/lib/dm-core/type_map.rb +0 -80
- data/lib/dm-core/types.rb +0 -19
- data/script/all +0 -4
- data/spec/integration/association_spec.rb +0 -1382
- data/spec/integration/association_through_spec.rb +0 -203
- data/spec/integration/associations/many_to_many_spec.rb +0 -449
- data/spec/integration/associations/many_to_one_spec.rb +0 -163
- data/spec/integration/associations/one_to_many_spec.rb +0 -188
- data/spec/integration/auto_migrations_spec.rb +0 -413
- data/spec/integration/collection_spec.rb +0 -1073
- data/spec/integration/data_objects_adapter_spec.rb +0 -32
- data/spec/integration/dependency_queue_spec.rb +0 -46
- data/spec/integration/model_spec.rb +0 -197
- data/spec/integration/mysql_adapter_spec.rb +0 -85
- data/spec/integration/postgres_adapter_spec.rb +0 -731
- data/spec/integration/property_spec.rb +0 -253
- data/spec/integration/query_spec.rb +0 -514
- data/spec/integration/repository_spec.rb +0 -61
- data/spec/integration/resource_spec.rb +0 -513
- data/spec/integration/sqlite3_adapter_spec.rb +0 -352
- data/spec/integration/sti_spec.rb +0 -273
- data/spec/integration/strategic_eager_loading_spec.rb +0 -156
- data/spec/integration/transaction_spec.rb +0 -75
- data/spec/integration/type_spec.rb +0 -275
- data/spec/lib/logging_helper.rb +0 -18
- data/spec/lib/mock_adapter.rb +0 -27
- data/spec/lib/model_loader.rb +0 -100
- data/spec/lib/publicize_methods.rb +0 -28
- data/spec/models/content.rb +0 -16
- data/spec/models/vehicles.rb +0 -34
- data/spec/models/zoo.rb +0 -48
- data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
- data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
- data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
- data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
- data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
- data/spec/unit/associations/many_to_many_spec.rb +0 -32
- data/spec/unit/associations/many_to_one_spec.rb +0 -159
- data/spec/unit/associations/one_to_many_spec.rb +0 -393
- data/spec/unit/associations/one_to_one_spec.rb +0 -7
- data/spec/unit/associations/relationship_spec.rb +0 -71
- data/spec/unit/associations_spec.rb +0 -242
- data/spec/unit/auto_migrations_spec.rb +0 -111
- data/spec/unit/collection_spec.rb +0 -182
- data/spec/unit/data_mapper_spec.rb +0 -35
- data/spec/unit/identity_map_spec.rb +0 -126
- data/spec/unit/is_spec.rb +0 -80
- data/spec/unit/migrator_spec.rb +0 -33
- data/spec/unit/model_spec.rb +0 -321
- data/spec/unit/naming_conventions_spec.rb +0 -36
- data/spec/unit/property_set_spec.rb +0 -90
- data/spec/unit/property_spec.rb +0 -753
- data/spec/unit/query_spec.rb +0 -571
- data/spec/unit/repository_spec.rb +0 -93
- data/spec/unit/resource_spec.rb +0 -649
- data/spec/unit/scope_spec.rb +0 -142
- data/spec/unit/transaction_spec.rb +0 -493
- data/spec/unit/type_map_spec.rb +0 -114
- data/spec/unit/type_spec.rb +0 -119
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
class Query
|
|
3
|
+
# The Conditions module contains classes used as part of a Query when
|
|
4
|
+
# filtering collections of resources.
|
|
5
|
+
#
|
|
6
|
+
# The Conditions module contains two types of class used for filtering
|
|
7
|
+
# queries: Comparison and Operation. Although these are used on all
|
|
8
|
+
# repositorie types -- not just SQL-based repos -- these classes are best
|
|
9
|
+
# thought of as being the DataMapper counterpart to an SQL WHERE clause.
|
|
10
|
+
#
|
|
11
|
+
# Comparisons compare properties and relationships with values, while
|
|
12
|
+
# operations tie Comparisons together to form more complex expressions.
|
|
13
|
+
#
|
|
14
|
+
# For example, the following SQL query fragment:
|
|
15
|
+
#
|
|
16
|
+
# ... WHERE my_field = my_value AND another_field = another_value ...
|
|
17
|
+
#
|
|
18
|
+
# ... would be represented as two EqualToComparison instances tied
|
|
19
|
+
# together with an AndOperation.
|
|
20
|
+
#
|
|
21
|
+
# Conditions -- together with the Query class -- allow DataMapper to
|
|
22
|
+
# represent SQL-like expressions in an ORM-agnostic manner, and are used
|
|
23
|
+
# for both in-memory filtering of loaded Collection instances, and by
|
|
24
|
+
# adapters to retrieve records directly from your repositories.
|
|
25
|
+
#
|
|
26
|
+
# The classes contained in the Conditions module are for internal use by
|
|
27
|
+
# DataMapper and DataMapper plugins, and are not intended to be used
|
|
28
|
+
# directly in your applications.
|
|
29
|
+
module Conditions
|
|
30
|
+
|
|
31
|
+
# An abstract class which provides easy access to comparison operators
|
|
32
|
+
#
|
|
33
|
+
# @example Creating a new comparison
|
|
34
|
+
# Comparison.new(:eql, MyClass.my_property, "value")
|
|
35
|
+
#
|
|
36
|
+
class Comparison
|
|
37
|
+
|
|
38
|
+
# Creates a new Comparison instance
|
|
39
|
+
#
|
|
40
|
+
# The returned instance will be suitable for matching the given
|
|
41
|
+
# subject (property or relationship) against the value.
|
|
42
|
+
#
|
|
43
|
+
# @param [Symbol] slug
|
|
44
|
+
# The type of comparison operator required. One of: :eql, :in, :gt,
|
|
45
|
+
# :gte, :lt, :lte, :regexp, :like.
|
|
46
|
+
# @param [Property, Associations::Relationship]
|
|
47
|
+
# The subject of the comparison - the value of the subject will be
|
|
48
|
+
# matched against the given value parameter.
|
|
49
|
+
# @param [Object] value
|
|
50
|
+
# The value for the comparison.
|
|
51
|
+
#
|
|
52
|
+
# @return [DataMapper::Query::Conditions::AbstractComparison]
|
|
53
|
+
#
|
|
54
|
+
# @example
|
|
55
|
+
# Comparison.new(:eql, MyClass.properties[:id], 1)
|
|
56
|
+
#
|
|
57
|
+
# @api semipublic
|
|
58
|
+
def self.new(slug, subject, value)
|
|
59
|
+
if klass = comparison_class(slug)
|
|
60
|
+
klass.new(subject, value)
|
|
61
|
+
else
|
|
62
|
+
raise ArgumentError,
|
|
63
|
+
"No Comparison class for `#{slug.inspect}' has been defined"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns the comparison class identified by the given slug
|
|
68
|
+
#
|
|
69
|
+
# @param [Symbol] slug
|
|
70
|
+
# See slug parameter for Comparison.new
|
|
71
|
+
#
|
|
72
|
+
# @return [AbstractComparison, nil]
|
|
73
|
+
#
|
|
74
|
+
# @api private
|
|
75
|
+
def self.comparison_class(slug)
|
|
76
|
+
comparison_classes[slug] ||=
|
|
77
|
+
AbstractComparison.descendants.detect do |comparison_class|
|
|
78
|
+
comparison_class.slug == slug
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns an array of all slugs registered with Comparison
|
|
83
|
+
#
|
|
84
|
+
# @return [Array<Symbol>]
|
|
85
|
+
#
|
|
86
|
+
# @api private
|
|
87
|
+
def self.slugs
|
|
88
|
+
@slugs ||=
|
|
89
|
+
AbstractComparison.descendants.map do |comparison_class|
|
|
90
|
+
comparison_class.slug
|
|
91
|
+
end.freeze
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class << self
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
# Holds comparison subclasses keyed on their slug
|
|
98
|
+
#
|
|
99
|
+
# @return [Hash]
|
|
100
|
+
#
|
|
101
|
+
# @api private
|
|
102
|
+
def comparison_classes
|
|
103
|
+
@comparison_classes ||= {}
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end # class Comparison
|
|
107
|
+
|
|
108
|
+
# A base class for the various comparison classes.
|
|
109
|
+
class AbstractComparison
|
|
110
|
+
extend Deprecate
|
|
111
|
+
extend Equalizer
|
|
112
|
+
|
|
113
|
+
deprecate :property, :subject
|
|
114
|
+
|
|
115
|
+
equalize :slug, :subject, :value
|
|
116
|
+
|
|
117
|
+
# The property or relationship which is being matched against
|
|
118
|
+
#
|
|
119
|
+
# @return [Property, Associations::Relationship]
|
|
120
|
+
#
|
|
121
|
+
# @api semipublic
|
|
122
|
+
attr_reader :subject
|
|
123
|
+
|
|
124
|
+
# Value to be compared with the subject
|
|
125
|
+
#
|
|
126
|
+
# This value is compared against that contained in the subject when
|
|
127
|
+
# filtering collections, or the value in the repository when
|
|
128
|
+
# performing queries.
|
|
129
|
+
#
|
|
130
|
+
# In the case of custom types, this is the value as it is stored in
|
|
131
|
+
# the repository.
|
|
132
|
+
#
|
|
133
|
+
# @return [Object]
|
|
134
|
+
#
|
|
135
|
+
# @api semipublic
|
|
136
|
+
attr_reader :value
|
|
137
|
+
|
|
138
|
+
# The loaded/typecast value
|
|
139
|
+
#
|
|
140
|
+
# In the case of primitive types, this will be the same as +value+,
|
|
141
|
+
# however when using custom types this stores the loaded value.
|
|
142
|
+
#
|
|
143
|
+
# If writing an adapter, you should use +value+, while plugin authors
|
|
144
|
+
# should refer to +loaded_value+.
|
|
145
|
+
#
|
|
146
|
+
#--
|
|
147
|
+
# As an example, you might use symbols with the Enum type in dm-types
|
|
148
|
+
#
|
|
149
|
+
# property :myprop, Enum[:open, :closed]
|
|
150
|
+
#
|
|
151
|
+
# These are stored in repositories as 1 and 2, respectively. +value+
|
|
152
|
+
# returns the 1 or 2, while +loaded_value+ returns the symbol.
|
|
153
|
+
#++
|
|
154
|
+
#
|
|
155
|
+
# @return [Object]
|
|
156
|
+
#
|
|
157
|
+
# @api semipublic
|
|
158
|
+
attr_reader :loaded_value
|
|
159
|
+
|
|
160
|
+
# Keeps track of AbstractComparison subclasses (used in Comparison)
|
|
161
|
+
#
|
|
162
|
+
# @return [Set<AbstractComparison>]
|
|
163
|
+
# @api private
|
|
164
|
+
def self.descendants
|
|
165
|
+
@descendants ||= Set.new
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Registers AbstractComparison subclasses (used in Comparison)
|
|
169
|
+
#
|
|
170
|
+
# @api private
|
|
171
|
+
def self.inherited(comparison_class)
|
|
172
|
+
descendants << comparison_class
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Setter/getter: allows subclasses to easily set their slug
|
|
176
|
+
#
|
|
177
|
+
# @param [Symbol] slug
|
|
178
|
+
# The slug to be set for this class. Passing nil returns the current
|
|
179
|
+
# value instead.
|
|
180
|
+
#
|
|
181
|
+
# @return [Symbol]
|
|
182
|
+
# The current slug set for the Comparison.
|
|
183
|
+
#
|
|
184
|
+
# @example Creating a MyComparison compairson with slug :exact.
|
|
185
|
+
# class MyComparison < AbstractComparison
|
|
186
|
+
# slug :exact
|
|
187
|
+
# end
|
|
188
|
+
#
|
|
189
|
+
# @api semipublic
|
|
190
|
+
def self.slug(slug = nil)
|
|
191
|
+
slug ? @slug = slug : @slug
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Return the comparison class slug
|
|
195
|
+
#
|
|
196
|
+
# @return [Symbol]
|
|
197
|
+
# the comparison class slug
|
|
198
|
+
#
|
|
199
|
+
# @api private
|
|
200
|
+
def slug
|
|
201
|
+
self.class.slug
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Tests that the Comparison is valid
|
|
205
|
+
#
|
|
206
|
+
# Subclasses can overload this to customise the means by which they
|
|
207
|
+
# determine the validity of the comparison. #valid? is called prior to
|
|
208
|
+
# performing a query on the repository: each Comparison within a Query
|
|
209
|
+
# must be valid otherwise the query will not be performed.
|
|
210
|
+
#
|
|
211
|
+
# @see DataMapper::Property#valid?
|
|
212
|
+
# @see DataMapper::Associations::Relationship#valid?
|
|
213
|
+
#
|
|
214
|
+
# @return [Boolean]
|
|
215
|
+
#
|
|
216
|
+
# @api semipublic
|
|
217
|
+
def valid?
|
|
218
|
+
# This needs to be deferred until the last moment because the value
|
|
219
|
+
# could be a reference to a Resource, that when the comparison was
|
|
220
|
+
# created was invalid, but has since been saved and has it's key
|
|
221
|
+
# set.
|
|
222
|
+
subject.valid?(loaded_value)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Returns whether the subject is a Relationship
|
|
226
|
+
#
|
|
227
|
+
# @return [Boolean]
|
|
228
|
+
#
|
|
229
|
+
# @api semipublic
|
|
230
|
+
def relationship?
|
|
231
|
+
false
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Returns whether the subject is a Property
|
|
235
|
+
#
|
|
236
|
+
# @return [Boolean]
|
|
237
|
+
#
|
|
238
|
+
# @api semipublic
|
|
239
|
+
def property?
|
|
240
|
+
subject.kind_of?(Property)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Returns a human-readable representation of this object
|
|
244
|
+
#
|
|
245
|
+
# @return [String]
|
|
246
|
+
#
|
|
247
|
+
# @api semipublic
|
|
248
|
+
def inspect
|
|
249
|
+
"#<#{self.class} @subject=#{@subject.inspect} " \
|
|
250
|
+
"@value=#{@value.inspect} @loaded_value=#{@loaded_value.inspect}>"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Returns a string version of this Comparison object
|
|
254
|
+
#
|
|
255
|
+
# @example
|
|
256
|
+
# Comparison.new(:==, MyClass.my_property, "value")
|
|
257
|
+
# # => "my_property == value"
|
|
258
|
+
#
|
|
259
|
+
# @return [String]
|
|
260
|
+
#
|
|
261
|
+
# @api semipublic
|
|
262
|
+
def to_s
|
|
263
|
+
"#{@subject} #{comparator_string} #{@value}"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
private # ============================================================
|
|
267
|
+
|
|
268
|
+
# Holds the actual value of the given property or relationship
|
|
269
|
+
#
|
|
270
|
+
# @return [Object]
|
|
271
|
+
#
|
|
272
|
+
# @api semipublic
|
|
273
|
+
attr_reader :expected
|
|
274
|
+
|
|
275
|
+
# Creates a new AbstractComparison instance with +subject+ and +value+
|
|
276
|
+
#
|
|
277
|
+
# @param [Property, Associations::Relationship] subject
|
|
278
|
+
# The subject of the comparison - the value of the subject will be
|
|
279
|
+
# matched against the given value parameter.
|
|
280
|
+
# @param [Object] value
|
|
281
|
+
# The value for the comparison.
|
|
282
|
+
#
|
|
283
|
+
# @api semipublic
|
|
284
|
+
def initialize(subject, value)
|
|
285
|
+
@subject = subject
|
|
286
|
+
@loaded_value = typecast_value(value)
|
|
287
|
+
@value = dumped_value(@loaded_value)
|
|
288
|
+
@expected = expected_value
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Used by Ruby when creating a copy of the comparison
|
|
292
|
+
#
|
|
293
|
+
# @api private
|
|
294
|
+
def initialize_copy(*)
|
|
295
|
+
@value = @value.dup
|
|
296
|
+
@loaded_value = @loaded_value.dup
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Typecasts the given +val+ using subject#typecast
|
|
300
|
+
#
|
|
301
|
+
# If the subject has no typecast method the value is returned without
|
|
302
|
+
# any changes.
|
|
303
|
+
#
|
|
304
|
+
# @param [Object] val
|
|
305
|
+
# The object to attempt to typecast.
|
|
306
|
+
#
|
|
307
|
+
# @return [Object]
|
|
308
|
+
# The typecasted object.
|
|
309
|
+
#
|
|
310
|
+
# @see Property#typecast
|
|
311
|
+
#
|
|
312
|
+
# @api private
|
|
313
|
+
def typecast_value(val)
|
|
314
|
+
if subject.respond_to?(:typecast)
|
|
315
|
+
subject.typecast(val)
|
|
316
|
+
else
|
|
317
|
+
val
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Dumps the given +val+ using subject#value
|
|
322
|
+
#
|
|
323
|
+
# This converts property values to the primitive as stored in the
|
|
324
|
+
# repository.
|
|
325
|
+
#
|
|
326
|
+
# @param [Object] val
|
|
327
|
+
# The object to attempt to typecast.
|
|
328
|
+
#
|
|
329
|
+
# @return [Object]
|
|
330
|
+
# The raw (dumped) object.
|
|
331
|
+
#
|
|
332
|
+
# @see Property#value
|
|
333
|
+
#
|
|
334
|
+
# @api private
|
|
335
|
+
def dumped_value(val)
|
|
336
|
+
if subject.respond_to?(:value)
|
|
337
|
+
subject.value(val)
|
|
338
|
+
else
|
|
339
|
+
val
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Returns a value for the comparison +subject+
|
|
344
|
+
#
|
|
345
|
+
# Extracts value for the +subject+ property or relationship from the
|
|
346
|
+
# given +record+, where +record+ is a Resource instance or a Hash.
|
|
347
|
+
#
|
|
348
|
+
# @param [DataMapper::Resource, Hash] record
|
|
349
|
+
# The resource or hash from which to retrieve the value.
|
|
350
|
+
# @param [Property, Associations::Relationship]
|
|
351
|
+
# The subject of the comparison. For example, if this is a property,
|
|
352
|
+
# the value for the resources +subject+ property is retrieved.
|
|
353
|
+
# @param [Symbol] key_type
|
|
354
|
+
# In the event that +subject+ is a relationship, key_type indicated
|
|
355
|
+
# which key should be used to retrieve the value from the resource.
|
|
356
|
+
#
|
|
357
|
+
# @return [Object]
|
|
358
|
+
#
|
|
359
|
+
# @api semipublic
|
|
360
|
+
def record_value(record, subject = @subject, key_type = :source_key)
|
|
361
|
+
case record
|
|
362
|
+
when Hash
|
|
363
|
+
record_value_from_hash(record, subject, key_type)
|
|
364
|
+
when Resource
|
|
365
|
+
record_value_from_resource(record, subject, key_type)
|
|
366
|
+
else
|
|
367
|
+
record
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Returns a value from a record hash
|
|
372
|
+
#
|
|
373
|
+
# Retrieves value for the +subject+ property or relationship from the
|
|
374
|
+
# given +hash+.
|
|
375
|
+
#
|
|
376
|
+
# @return [Object]
|
|
377
|
+
#
|
|
378
|
+
# @see AbstractComparison#record_value
|
|
379
|
+
#
|
|
380
|
+
# @api private
|
|
381
|
+
def record_value_from_hash(hash, subject, key_type)
|
|
382
|
+
hash.fetch subject, case subject
|
|
383
|
+
when Property
|
|
384
|
+
hash[subject.field]
|
|
385
|
+
when Associations::Relationship
|
|
386
|
+
subject.send(key_type).map { |property|
|
|
387
|
+
record_value_from_hash(hash, property, key_type)
|
|
388
|
+
}
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Returns a value from a resource
|
|
393
|
+
#
|
|
394
|
+
# Extracts value for the +subject+ property or relationship from the
|
|
395
|
+
# given +resource+.
|
|
396
|
+
#
|
|
397
|
+
# @return [Object]
|
|
398
|
+
#
|
|
399
|
+
# @see AbstractComparison#record_value
|
|
400
|
+
#
|
|
401
|
+
# @api private
|
|
402
|
+
def record_value_from_resource(resource, subject, key_type)
|
|
403
|
+
case subject
|
|
404
|
+
when Property
|
|
405
|
+
subject.get!(resource)
|
|
406
|
+
when Associations::Relationship
|
|
407
|
+
subject.send(key_type).get!(resource)
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Retrieves the value of the +subject+
|
|
412
|
+
#
|
|
413
|
+
# @return [Object]
|
|
414
|
+
#
|
|
415
|
+
# @api semipublic
|
|
416
|
+
def expected_value(val = @loaded_value)
|
|
417
|
+
expected_value = record_value(val, @subject, :target_key)
|
|
418
|
+
|
|
419
|
+
if @subject.respond_to?(:source_key)
|
|
420
|
+
@subject.source_key.typecast(expected_value)
|
|
421
|
+
else
|
|
422
|
+
expected_value
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Returns the name of this comparison
|
|
427
|
+
#
|
|
428
|
+
# @return [String]
|
|
429
|
+
# The name of the comparison class minus the trailing "Comparison".
|
|
430
|
+
#
|
|
431
|
+
# @example
|
|
432
|
+
# Comparison.new(:eql, ...).comparator_string
|
|
433
|
+
# # => Equal
|
|
434
|
+
#
|
|
435
|
+
# @api private
|
|
436
|
+
def comparator_string
|
|
437
|
+
self.class.name.chomp('Comparison')
|
|
438
|
+
end
|
|
439
|
+
end # class AbstractComparison
|
|
440
|
+
|
|
441
|
+
# Included into comparisons which are capable of supporting
|
|
442
|
+
# Relationships.
|
|
443
|
+
module RelationshipHandler
|
|
444
|
+
# Returns whether this comparison subject is a Relationship
|
|
445
|
+
#
|
|
446
|
+
# @return [Boolean]
|
|
447
|
+
#
|
|
448
|
+
# @api semipublic
|
|
449
|
+
def relationship?
|
|
450
|
+
subject.kind_of?(Associations::Relationship)
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# Returns the conditions required to match the subject relationship
|
|
454
|
+
#
|
|
455
|
+
# @return [Hash]
|
|
456
|
+
#
|
|
457
|
+
# @api semipublic
|
|
458
|
+
def foreign_key_mapping
|
|
459
|
+
relationship = subject.inverse
|
|
460
|
+
|
|
461
|
+
Query.target_conditions(value, relationship.source_key, relationship.target_key)
|
|
462
|
+
end
|
|
463
|
+
end # module RelationshipHandler
|
|
464
|
+
|
|
465
|
+
# Tests whether the value in the record is equal to the expected_value
|
|
466
|
+
# set for the Comparison.
|
|
467
|
+
class EqualToComparison < AbstractComparison
|
|
468
|
+
include RelationshipHandler
|
|
469
|
+
|
|
470
|
+
slug :eql
|
|
471
|
+
|
|
472
|
+
# Asserts that the record value matches the comparison
|
|
473
|
+
#
|
|
474
|
+
# @param [Resource, Hash] record
|
|
475
|
+
# The record containing the value to be matched
|
|
476
|
+
#
|
|
477
|
+
# @return [Boolean]
|
|
478
|
+
# @api semipublic
|
|
479
|
+
def matches?(record)
|
|
480
|
+
record_value(record) == expected
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
private
|
|
484
|
+
|
|
485
|
+
# @return [String]
|
|
486
|
+
#
|
|
487
|
+
# @see AbstractComparison#to_s
|
|
488
|
+
#
|
|
489
|
+
# @api private
|
|
490
|
+
def comparator_string
|
|
491
|
+
'='
|
|
492
|
+
end
|
|
493
|
+
end # class EqualToComparison
|
|
494
|
+
|
|
495
|
+
# Tests whether the value in the record is contained in the
|
|
496
|
+
# expected_value set for the Comparison, where expected_value is an
|
|
497
|
+
# Array, Range, or Set.
|
|
498
|
+
class InclusionComparison < AbstractComparison
|
|
499
|
+
include RelationshipHandler
|
|
500
|
+
|
|
501
|
+
slug :in
|
|
502
|
+
|
|
503
|
+
# Asserts that the record value matches the comparison
|
|
504
|
+
#
|
|
505
|
+
# @param [Resource, Hash] record
|
|
506
|
+
# The record containing the value to be matched
|
|
507
|
+
#
|
|
508
|
+
# @return [Boolean]
|
|
509
|
+
#
|
|
510
|
+
# @api semipublic
|
|
511
|
+
def matches?(record)
|
|
512
|
+
record_value = record_value(record)
|
|
513
|
+
!record_value.nil? && expected.include?(record_value)
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Checks that the Comparison is valid
|
|
517
|
+
#
|
|
518
|
+
# @see DataMapper::Query::Conditions::AbstractComparison#valid?
|
|
519
|
+
#
|
|
520
|
+
# @return [Boolean]
|
|
521
|
+
#
|
|
522
|
+
# @api semipublic
|
|
523
|
+
def valid?
|
|
524
|
+
case value
|
|
525
|
+
when Array, Set
|
|
526
|
+
loaded_value.any? && loaded_value.all? { |val| subject.valid?(val) }
|
|
527
|
+
when Range
|
|
528
|
+
loaded_value.any? && subject.valid?(loaded_value.first) && subject.valid?(loaded_value.last)
|
|
529
|
+
else
|
|
530
|
+
false
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
private
|
|
535
|
+
|
|
536
|
+
# Overloads AbtractComparison#expected_value
|
|
537
|
+
#
|
|
538
|
+
# @return [Array<Object>]
|
|
539
|
+
# @see AbtractComparison#expected_value
|
|
540
|
+
#
|
|
541
|
+
# @api private
|
|
542
|
+
def expected_value
|
|
543
|
+
if loaded_value.is_a?(Range)
|
|
544
|
+
Range.new(super(loaded_value.first), super(loaded_value.last), loaded_value.exclude_end?)
|
|
545
|
+
else
|
|
546
|
+
loaded_value.map { |val| super(val) }
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# Typecasts each value in the inclusion set
|
|
551
|
+
#
|
|
552
|
+
# @return [Array<Object>]
|
|
553
|
+
#
|
|
554
|
+
# @see AbtractComparison#typecast_value
|
|
555
|
+
#
|
|
556
|
+
# @api private
|
|
557
|
+
def typecast_value(val)
|
|
558
|
+
if subject.respond_to?(:typecast) && val.is_a?(Range)
|
|
559
|
+
if subject.primitive?(val.first)
|
|
560
|
+
# If the range type matches, nothing to do
|
|
561
|
+
val
|
|
562
|
+
else
|
|
563
|
+
# Create a new range with the new type
|
|
564
|
+
Range.new(subject.typecast(val.first), subject.typecast(val.last), val.exclude_end?)
|
|
565
|
+
end
|
|
566
|
+
elsif subject.respond_to?(:typecast) && val.respond_to?(:map)
|
|
567
|
+
val.map { |el| subject.typecast(el) }
|
|
568
|
+
else
|
|
569
|
+
val
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# Dumps the given +val+ using subject#value
|
|
574
|
+
#
|
|
575
|
+
# @return [Array<Object>]
|
|
576
|
+
#
|
|
577
|
+
# @see AbtractComparison#dumped_value
|
|
578
|
+
#
|
|
579
|
+
# @api private
|
|
580
|
+
def dumped_value(val)
|
|
581
|
+
if subject.respond_to?(:value) && val.is_a?(Range) && !subject.custom?
|
|
582
|
+
val
|
|
583
|
+
elsif subject.respond_to?(:value) && val.respond_to?(:map)
|
|
584
|
+
val.map { |el| subject.value(el) }
|
|
585
|
+
else
|
|
586
|
+
val
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# @return [String]
|
|
591
|
+
#
|
|
592
|
+
# @see AbstractComparison#to_s
|
|
593
|
+
#
|
|
594
|
+
# @api private
|
|
595
|
+
def comparator_string
|
|
596
|
+
'IN'
|
|
597
|
+
end
|
|
598
|
+
end # class InclusionComparison
|
|
599
|
+
|
|
600
|
+
# Tests whether the value in the record matches the expected_value
|
|
601
|
+
# regexp set for the Comparison.
|
|
602
|
+
class RegexpComparison < AbstractComparison
|
|
603
|
+
slug :regexp
|
|
604
|
+
|
|
605
|
+
# Asserts that the record value matches the comparison
|
|
606
|
+
#
|
|
607
|
+
# @param [Resource, Hash] record
|
|
608
|
+
# The record containing the value to be matched
|
|
609
|
+
#
|
|
610
|
+
# @return [Boolean]
|
|
611
|
+
#
|
|
612
|
+
# @api semipublic
|
|
613
|
+
def matches?(record)
|
|
614
|
+
record_value = record_value(record)
|
|
615
|
+
!record_value.nil? && record_value =~ expected
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# Checks that the Comparison is valid
|
|
619
|
+
#
|
|
620
|
+
# @see AbstractComparison#valid?
|
|
621
|
+
#
|
|
622
|
+
# @api semipublic
|
|
623
|
+
def valid?
|
|
624
|
+
value.kind_of?(Regexp)
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
private
|
|
628
|
+
|
|
629
|
+
# Returns the value untouched
|
|
630
|
+
#
|
|
631
|
+
# @return [Object]
|
|
632
|
+
#
|
|
633
|
+
# @api private
|
|
634
|
+
def typecast_value(val)
|
|
635
|
+
val
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
# @return [String]
|
|
639
|
+
#
|
|
640
|
+
# @see AbstractComparison#to_s
|
|
641
|
+
#
|
|
642
|
+
# @api private
|
|
643
|
+
def comparator_string
|
|
644
|
+
'=~'
|
|
645
|
+
end
|
|
646
|
+
end # class RegexpComparison
|
|
647
|
+
|
|
648
|
+
# Tests whether the value in the record is like the expected_value set
|
|
649
|
+
# for the Comparison. Equivalent to a LIKE clause in an SQL database.
|
|
650
|
+
#
|
|
651
|
+
# TODO: move this to dm-more with DataObjectsAdapter plugins
|
|
652
|
+
class LikeComparison < AbstractComparison
|
|
653
|
+
slug :like
|
|
654
|
+
|
|
655
|
+
# Asserts that the record value matches the comparison
|
|
656
|
+
#
|
|
657
|
+
# @param [Resource, Hash] record
|
|
658
|
+
# The record containing the value to be matched
|
|
659
|
+
#
|
|
660
|
+
# @return [Boolean]
|
|
661
|
+
#
|
|
662
|
+
# @api semipublic
|
|
663
|
+
def matches?(record)
|
|
664
|
+
record_value = record_value(record)
|
|
665
|
+
!record_value.nil? && record_value =~ expected
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
private
|
|
669
|
+
|
|
670
|
+
# Overloads the +expected_value+ method in AbstractComparison
|
|
671
|
+
#
|
|
672
|
+
# Return a regular expression suitable for matching against the
|
|
673
|
+
# records value.
|
|
674
|
+
#
|
|
675
|
+
# @return [Regexp]
|
|
676
|
+
#
|
|
677
|
+
# @see AbtractComparison#expected_value
|
|
678
|
+
#
|
|
679
|
+
# @api semipublic
|
|
680
|
+
def expected_value
|
|
681
|
+
Regexp.new(@value.to_s.gsub('%', '.*').gsub('_', '.'))
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
# @return [String]
|
|
685
|
+
#
|
|
686
|
+
# @see AbstractComparison#to_s
|
|
687
|
+
#
|
|
688
|
+
# @api private
|
|
689
|
+
def comparator_string
|
|
690
|
+
'LIKE'
|
|
691
|
+
end
|
|
692
|
+
end # class LikeComparison
|
|
693
|
+
|
|
694
|
+
# Tests whether the value in the record is greater than the
|
|
695
|
+
# expected_value set for the Comparison.
|
|
696
|
+
class GreaterThanComparison < AbstractComparison
|
|
697
|
+
slug :gt
|
|
698
|
+
|
|
699
|
+
# Asserts that the record value matches the comparison
|
|
700
|
+
#
|
|
701
|
+
# @param [Resource, Hash] record
|
|
702
|
+
# The record containing the value to be matched
|
|
703
|
+
#
|
|
704
|
+
# @return [Boolean]
|
|
705
|
+
#
|
|
706
|
+
# @api semipublic
|
|
707
|
+
def matches?(record)
|
|
708
|
+
record_value = record_value(record)
|
|
709
|
+
!record_value.nil? && record_value > expected
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
private
|
|
713
|
+
|
|
714
|
+
# @return [String]
|
|
715
|
+
#
|
|
716
|
+
# @see AbstractComparison#to_s
|
|
717
|
+
#
|
|
718
|
+
# @api private
|
|
719
|
+
def comparator_string
|
|
720
|
+
'>'
|
|
721
|
+
end
|
|
722
|
+
end # class GreaterThanComparison
|
|
723
|
+
|
|
724
|
+
# Tests whether the value in the record is less than the expected_value
|
|
725
|
+
# set for the Comparison.
|
|
726
|
+
class LessThanComparison < AbstractComparison
|
|
727
|
+
slug :lt
|
|
728
|
+
|
|
729
|
+
# Asserts that the record value matches the comparison
|
|
730
|
+
#
|
|
731
|
+
# @param [Resource, Hash] record
|
|
732
|
+
# The record containing the value to be matched
|
|
733
|
+
#
|
|
734
|
+
# @return [Boolean]
|
|
735
|
+
#
|
|
736
|
+
# @api semipublic
|
|
737
|
+
def matches?(record)
|
|
738
|
+
record_value = record_value(record)
|
|
739
|
+
!record_value.nil? && record_value < expected
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
private
|
|
743
|
+
|
|
744
|
+
# @return [String]
|
|
745
|
+
#
|
|
746
|
+
# @see AbstractComparison#to_s
|
|
747
|
+
#
|
|
748
|
+
# @api private
|
|
749
|
+
def comparator_string
|
|
750
|
+
'<'
|
|
751
|
+
end
|
|
752
|
+
end # class LessThanComparison
|
|
753
|
+
|
|
754
|
+
# Tests whether the value in the record is greater than, or equal to,
|
|
755
|
+
# the expected_value set for the Comparison.
|
|
756
|
+
class GreaterThanOrEqualToComparison < AbstractComparison
|
|
757
|
+
slug :gte
|
|
758
|
+
|
|
759
|
+
# Asserts that the record value matches the comparison
|
|
760
|
+
#
|
|
761
|
+
# @param [Resource, Hash] record
|
|
762
|
+
# The record containing the value to be matched
|
|
763
|
+
#
|
|
764
|
+
# @return [Boolean]
|
|
765
|
+
#
|
|
766
|
+
# @api semipublic
|
|
767
|
+
def matches?(record)
|
|
768
|
+
record_value = record_value(record)
|
|
769
|
+
!record_value.nil? && record_value >= expected
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
private
|
|
773
|
+
|
|
774
|
+
# @see AbstractComparison#to_s
|
|
775
|
+
#
|
|
776
|
+
# @api private
|
|
777
|
+
def comparator_string
|
|
778
|
+
'>='
|
|
779
|
+
end
|
|
780
|
+
end # class GreaterThanOrEqualToComparison
|
|
781
|
+
|
|
782
|
+
# Tests whether the value in the record is less than, or equal to, the
|
|
783
|
+
# expected_value set for the Comparison.
|
|
784
|
+
class LessThanOrEqualToComparison < AbstractComparison
|
|
785
|
+
slug :lte
|
|
786
|
+
|
|
787
|
+
# Asserts that the record value matches the comparison
|
|
788
|
+
#
|
|
789
|
+
# @param [Resource, Hash] record
|
|
790
|
+
# The record containing the value to be matched
|
|
791
|
+
#
|
|
792
|
+
# @return [Boolean]
|
|
793
|
+
#
|
|
794
|
+
# @api semipublic
|
|
795
|
+
def matches?(record)
|
|
796
|
+
record_value = record_value(record)
|
|
797
|
+
!record_value.nil? && record_value <= expected
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
private
|
|
801
|
+
|
|
802
|
+
# @return [String]
|
|
803
|
+
#
|
|
804
|
+
# @see AbstractComparison#to_s
|
|
805
|
+
#
|
|
806
|
+
# @api private
|
|
807
|
+
def comparator_string
|
|
808
|
+
'<='
|
|
809
|
+
end
|
|
810
|
+
end # class LessThanOrEqualToComparison
|
|
811
|
+
|
|
812
|
+
end # module Conditions
|
|
813
|
+
end # class Query
|
|
814
|
+
end # module DataMapper
|