mongodoc 0.2.2 → 0.2.4
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/Rakefile +21 -0
- data/TODO +6 -1
- data/VERSION +1 -1
- data/features/finders.feature +1 -1
- data/features/mongodoc_base.feature +11 -2
- data/features/{named_scopes.feature → scopes.feature} +0 -0
- data/features/step_definitions/document_steps.rb +15 -4
- data/features/step_definitions/documents.rb +3 -3
- data/features/step_definitions/query_steps.rb +17 -14
- data/features/step_definitions/{named_scope_steps.rb → scope_steps.rb} +0 -0
- data/features/using_criteria.feature +22 -43
- data/lib/mongodoc.rb +1 -1
- data/lib/mongodoc/associations/collection_proxy.rb +3 -1
- data/lib/mongodoc/associations/document_proxy.rb +4 -1
- data/lib/mongodoc/associations/hash_proxy.rb +3 -1
- data/lib/mongodoc/associations/proxy_base.rb +6 -4
- data/lib/mongodoc/attributes.rb +6 -6
- data/lib/mongodoc/contexts.rb +24 -0
- data/lib/mongodoc/contexts/enumerable.rb +132 -0
- data/lib/mongodoc/contexts/mongo.rb +215 -0
- data/lib/mongodoc/criteria.rb +36 -479
- data/lib/mongodoc/document.rb +3 -2
- data/lib/mongodoc/finders.rb +31 -11
- data/lib/mongodoc/matchers.rb +35 -0
- data/lib/mongodoc/scope.rb +64 -0
- data/lib/mongoid/contexts/paging.rb +42 -0
- data/lib/mongoid/criteria.rb +264 -0
- data/lib/mongoid/criterion/complex.rb +21 -0
- data/lib/mongoid/criterion/exclusion.rb +65 -0
- data/lib/mongoid/criterion/inclusion.rb +92 -0
- data/lib/mongoid/criterion/optional.rb +136 -0
- data/lib/mongoid/extensions/hash/criteria_helpers.rb +20 -0
- data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
- data/lib/mongoid/matchers/all.rb +11 -0
- data/lib/mongoid/matchers/default.rb +26 -0
- data/lib/mongoid/matchers/exists.rb +13 -0
- data/lib/mongoid/matchers/gt.rb +11 -0
- data/lib/mongoid/matchers/gte.rb +11 -0
- data/lib/mongoid/matchers/in.rb +11 -0
- data/lib/mongoid/matchers/lt.rb +11 -0
- data/lib/mongoid/matchers/lte.rb +11 -0
- data/lib/mongoid/matchers/ne.rb +11 -0
- data/lib/mongoid/matchers/nin.rb +11 -0
- data/lib/mongoid/matchers/size.rb +11 -0
- data/mongodoc.gemspec +39 -9
- data/spec/attributes_spec.rb +16 -2
- data/spec/contexts/enumerable_spec.rb +335 -0
- data/spec/contexts/mongo_spec.rb +148 -0
- data/spec/contexts_spec.rb +28 -0
- data/spec/criteria_spec.rb +15 -766
- data/spec/finders_spec.rb +28 -36
- data/spec/matchers_spec.rb +342 -0
- data/spec/scope_spec.rb +79 -0
- metadata +40 -10
- data/features/step_definitions/criteria_steps.rb +0 -42
- data/lib/mongodoc/named_scope.rb +0 -68
- data/spec/named_scope_spec.rb +0 -82
@@ -0,0 +1,215 @@
|
|
1
|
+
module MongoDoc
|
2
|
+
module Contexts
|
3
|
+
class Mongo
|
4
|
+
include Mongoid::Contexts::Paging
|
5
|
+
attr_reader :criteria
|
6
|
+
|
7
|
+
delegate :klass, :options, :selector, :to => :criteria
|
8
|
+
delegate :collection, :to => :klass
|
9
|
+
|
10
|
+
AGGREGATE_REDUCE = "function(obj, prev) { prev.count++; }"
|
11
|
+
# Aggregate the context. This will take the internally built selector and options
|
12
|
+
# and pass them on to the Ruby driver's +group()+ method on the collection. The
|
13
|
+
# collection itself will be retrieved from the class provided, and once the
|
14
|
+
# query has returned it will provided a grouping of keys with counts.
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
#
|
18
|
+
# <tt>context.aggregate</tt>
|
19
|
+
#
|
20
|
+
# Returns:
|
21
|
+
#
|
22
|
+
# A +Hash+ with field values as keys, counts as values
|
23
|
+
def aggregate
|
24
|
+
collection.group(options[:fields], selector, { :count => 0 }, AGGREGATE_REDUCE, true)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get the count of matching documents in the database for the context.
|
28
|
+
#
|
29
|
+
# Example:
|
30
|
+
#
|
31
|
+
# <tt>context.count</tt>
|
32
|
+
#
|
33
|
+
# Returns:
|
34
|
+
#
|
35
|
+
# An +Integer+ count of documents.
|
36
|
+
def count
|
37
|
+
@count ||= collection.find(selector, options).count
|
38
|
+
end
|
39
|
+
|
40
|
+
# Execute the context. This will take the selector and options
|
41
|
+
# and pass them on to the Ruby driver's +find()+ method on the collection. The
|
42
|
+
# collection itself will be retrieved from the class provided, and once the
|
43
|
+
# query has returned new documents of the type of class provided will be instantiated.
|
44
|
+
#
|
45
|
+
# Example:
|
46
|
+
#
|
47
|
+
# <tt>mongo.execute</tt>
|
48
|
+
#
|
49
|
+
# Returns:
|
50
|
+
#
|
51
|
+
# An enumerable +Cursor+.
|
52
|
+
def execute(paginating = false)
|
53
|
+
cursor = collection.find(selector, options)
|
54
|
+
if cursor
|
55
|
+
@count = cursor.count if paginating
|
56
|
+
cursor
|
57
|
+
else
|
58
|
+
[]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
GROUP_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
|
63
|
+
# Groups the context. This will take the internally built selector and options
|
64
|
+
# and pass them on to the Ruby driver's +group()+ method on the collection. The
|
65
|
+
# collection itself will be retrieved from the class provided, and once the
|
66
|
+
# query has returned it will provided a grouping of keys with objects.
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
#
|
70
|
+
# <tt>context.group</tt>
|
71
|
+
#
|
72
|
+
# Returns:
|
73
|
+
#
|
74
|
+
# A +Hash+ with field values as keys, arrays of documents as values.
|
75
|
+
def group
|
76
|
+
collection.group(
|
77
|
+
options[:fields],
|
78
|
+
selector,
|
79
|
+
{ :group => [] },
|
80
|
+
GROUP_REDUCE,
|
81
|
+
true
|
82
|
+
).collect {|docs| docs["group"] = MongoDoc::BSON.decode(docs["group"]); docs }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return documents based on an id search. Will handle if a single id has
|
86
|
+
# been passed or mulitple ids.
|
87
|
+
#
|
88
|
+
# Example:
|
89
|
+
#
|
90
|
+
# context.id_criteria([1, 2, 3])
|
91
|
+
#
|
92
|
+
# Returns:
|
93
|
+
#
|
94
|
+
# The single or multiple documents.
|
95
|
+
def id_criteria(params)
|
96
|
+
criteria.id(params)
|
97
|
+
params.is_a?(Array) ? criteria.entries : one
|
98
|
+
end
|
99
|
+
|
100
|
+
# Create the new mongo context. This will execute the queries given the
|
101
|
+
# selector and options against the database.
|
102
|
+
#
|
103
|
+
# Example:
|
104
|
+
#
|
105
|
+
# <tt>Mongoid::Contexts::Mongo.new(criteria)</tt>
|
106
|
+
def initialize(criteria)
|
107
|
+
@criteria = criteria
|
108
|
+
end
|
109
|
+
|
110
|
+
# Return the last result for the +Context+. Essentially does a find_one on
|
111
|
+
# the collection with the sorting reversed. If no sorting parameters have
|
112
|
+
# been provided it will default to ids.
|
113
|
+
#
|
114
|
+
# Example:
|
115
|
+
#
|
116
|
+
# <tt>context.last</tt>
|
117
|
+
#
|
118
|
+
# Returns:
|
119
|
+
#
|
120
|
+
# The last document in the collection.
|
121
|
+
def last
|
122
|
+
sorting = options[:sort] || [[:_id, :asc]]
|
123
|
+
options[:sort] = sorting.collect { |option| [ option[0], option[1].invert ] }
|
124
|
+
collection.find_one(selector, options)
|
125
|
+
end
|
126
|
+
|
127
|
+
MAX_REDUCE = "function(obj, prev) { if (prev.max == 'start') { prev.max = obj.[field]; } " +
|
128
|
+
"if (prev.max < obj.[field]) { prev.max = obj.[field]; } }"
|
129
|
+
# Return the max value for a field.
|
130
|
+
#
|
131
|
+
# This will take the internally built selector and options
|
132
|
+
# and pass them on to the Ruby driver's +group()+ method on the collection. The
|
133
|
+
# collection itself will be retrieved from the class provided, and once the
|
134
|
+
# query has returned it will provided a grouping of keys with sums.
|
135
|
+
#
|
136
|
+
# Example:
|
137
|
+
#
|
138
|
+
# <tt>context.max(:age)</tt>
|
139
|
+
#
|
140
|
+
# Returns:
|
141
|
+
#
|
142
|
+
# A numeric max value.
|
143
|
+
def max(field)
|
144
|
+
grouped(:max, field.to_s, MAX_REDUCE)
|
145
|
+
end
|
146
|
+
|
147
|
+
MIN_REDUCE = "function(obj, prev) { if (prev.min == 'start') { prev.min = obj.[field]; } " +
|
148
|
+
"if (prev.min > obj.[field]) { prev.min = obj.[field]; } }"
|
149
|
+
# Return the min value for a field.
|
150
|
+
#
|
151
|
+
# This will take the internally built selector and options
|
152
|
+
# and pass them on to the Ruby driver's +group()+ method on the collection. The
|
153
|
+
# collection itself will be retrieved from the class provided, and once the
|
154
|
+
# query has returned it will provided a grouping of keys with sums.
|
155
|
+
#
|
156
|
+
# Example:
|
157
|
+
#
|
158
|
+
# <tt>context.min(:age)</tt>
|
159
|
+
#
|
160
|
+
# Returns:
|
161
|
+
#
|
162
|
+
# A numeric minimum value.
|
163
|
+
def min(field)
|
164
|
+
grouped(:min, field.to_s, MIN_REDUCE)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Return the first result for the +Context+.
|
168
|
+
#
|
169
|
+
# Example:
|
170
|
+
#
|
171
|
+
# <tt>context.one</tt>
|
172
|
+
#
|
173
|
+
# Return:
|
174
|
+
#
|
175
|
+
# The first document in the collection.
|
176
|
+
def one
|
177
|
+
collection.find_one(selector, options)
|
178
|
+
end
|
179
|
+
|
180
|
+
alias :first :one
|
181
|
+
|
182
|
+
SUM_REDUCE = "function(obj, prev) { if (prev.sum == 'start') { prev.sum = 0; } prev.sum += obj.[field]; }"
|
183
|
+
# Sum the context.
|
184
|
+
#
|
185
|
+
# This will take the internally built selector and options
|
186
|
+
# and pass them on to the Ruby driver's +group()+ method on the collection. The
|
187
|
+
# collection itself will be retrieved from the class provided, and once the
|
188
|
+
# query has returned it will provided a grouping of keys with sums.
|
189
|
+
#
|
190
|
+
# Example:
|
191
|
+
#
|
192
|
+
# <tt>context.sum(:age)</tt>
|
193
|
+
#
|
194
|
+
# Returns:
|
195
|
+
#
|
196
|
+
# A numeric value that is the sum.
|
197
|
+
def sum(field)
|
198
|
+
grouped(:sum, field.to_s, SUM_REDUCE)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Common functionality for grouping operations. Currently used by min, max
|
202
|
+
# and sum. Will gsub the field name in the supplied reduce function.
|
203
|
+
def grouped(start, field, reduce)
|
204
|
+
result = collection.group(
|
205
|
+
nil,
|
206
|
+
selector,
|
207
|
+
{ start => "start" },
|
208
|
+
reduce.gsub("[field]", field),
|
209
|
+
true
|
210
|
+
)
|
211
|
+
result.empty? ? nil : result.first[start.to_s]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
data/lib/mongodoc/criteria.rb
CHANGED
@@ -1,481 +1,38 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
#
|
35
|
-
# klass: The class to execute on.
|
36
|
-
def initialize(klass)
|
37
|
-
@selector, @options, @klass = {}, {}, klass
|
38
|
-
end
|
39
|
-
|
40
|
-
# Returns true if the supplied +Enumerable+ or +Criteria+ is equal to the results
|
41
|
-
# of this +Criteria+ or the criteria itself.
|
42
|
-
#
|
43
|
-
# This will force a database load when called if an enumerable is passed.
|
44
|
-
#
|
45
|
-
# Options:
|
46
|
-
#
|
47
|
-
# other: The other +Enumerable+ or +Criteria+ to compare to.
|
48
|
-
def ==(other)
|
49
|
-
case other
|
50
|
-
when Criteria
|
51
|
-
self.selector == other.selector && self.options == other.options
|
52
|
-
when Enumerable
|
53
|
-
@collection ||= execute
|
54
|
-
return (collection == other)
|
55
|
-
else
|
56
|
-
return false
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
AGGREGATE_REDUCE = "function(obj, prev) { prev.count++; }"
|
61
|
-
# Aggregate the criteria. This will take the internally built selector and options
|
62
|
-
# and pass them on to the Ruby driver's +group()+ method on the collection. The
|
63
|
-
# collection itself will be retrieved from the class provided, and once the
|
64
|
-
# query has returned it will provided a grouping of keys with counts.
|
65
|
-
#
|
66
|
-
# Example:
|
67
|
-
#
|
68
|
-
# <tt>criteria.only(:field1).where(:field1 => "Title").aggregate</tt>
|
69
|
-
def aggregate
|
70
|
-
klass.collection.group(options[:fields], selector, { :count => 0 }, AGGREGATE_REDUCE, true)
|
71
|
-
end
|
72
|
-
|
73
|
-
# Get all the matching documents in the database for the +Criteria+.
|
74
|
-
#
|
75
|
-
# Example:
|
76
|
-
#
|
77
|
-
# <tt>criteria.all</tt>
|
78
|
-
#
|
79
|
-
# Returns: <tt>Array</tt>
|
80
|
-
def all
|
81
|
-
collect
|
82
|
-
end
|
83
|
-
|
84
|
-
# Get the count of matching documents in the database for the +Criteria+.
|
85
|
-
#
|
86
|
-
# Example:
|
87
|
-
#
|
88
|
-
# <tt>criteria.count</tt>
|
89
|
-
#
|
90
|
-
# Returns: <tt>Integer</tt>
|
91
|
-
def count
|
92
|
-
@count ||= klass.collection.find(selector, options.dup).count
|
93
|
-
end
|
94
|
-
|
95
|
-
# Iterate over each +Document+ in the results and pass each document to the
|
96
|
-
# block.
|
97
|
-
#
|
98
|
-
# Example:
|
99
|
-
#
|
100
|
-
# <tt>criteria.each { |doc| p doc }</tt>
|
101
|
-
def each(&block)
|
102
|
-
@collection ||= execute
|
103
|
-
if block_given?
|
104
|
-
container = []
|
105
|
-
collection.each do |item|
|
106
|
-
container << item
|
107
|
-
yield item
|
108
|
-
end
|
109
|
-
@collection = container
|
110
|
-
end
|
111
|
-
self
|
112
|
-
end
|
113
|
-
|
114
|
-
GROUP_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
|
115
|
-
# Groups the criteria. This will take the internally built selector and options
|
116
|
-
# and pass them on to the Ruby driver's +group()+ method on the collection. The
|
117
|
-
# collection itself will be retrieved from the class provided, and once the
|
118
|
-
# query has returned it will provided a grouping of keys with objects.
|
119
|
-
#
|
120
|
-
# Example:
|
121
|
-
#
|
122
|
-
# <tt>criteria.only(:field1).where(:field1 => "Title").group</tt>
|
123
|
-
def group
|
124
|
-
klass.collection.group(
|
125
|
-
options[:fields],
|
126
|
-
selector,
|
127
|
-
{ :group => [] },
|
128
|
-
GROUP_REDUCE,
|
129
|
-
true
|
130
|
-
).collect {|docs| docs["group"] = MongoDoc::BSON.decode(docs["group"]); docs }
|
131
|
-
end
|
132
|
-
|
133
|
-
# Return the last result for the +Criteria+. Essentially does a find_one on
|
134
|
-
# the collection with the sorting reversed. If no sorting parameters have
|
135
|
-
# been provided it will default to ids.
|
136
|
-
#
|
137
|
-
# Example:
|
138
|
-
#
|
139
|
-
# <tt>Criteria.only(:name).where(:name = "Chrissy").last</tt>
|
140
|
-
def last
|
141
|
-
opts = options.dup
|
142
|
-
sorting = opts[:sort]
|
143
|
-
sorting = [[:_id, :asc]] unless sorting
|
144
|
-
opts[:sort] = sorting.collect { |option| [ option.first, Criteria.invert(option.last) ] }
|
145
|
-
klass.collection.find_one(selector, opts)
|
146
|
-
end
|
147
|
-
|
148
|
-
# Return the first result for the +Criteria+.
|
149
|
-
#
|
150
|
-
# Example:
|
151
|
-
#
|
152
|
-
# <tt>Criteria.only(:name).where(:name = "Chrissy").one</tt>
|
153
|
-
def one
|
154
|
-
klass.collection.find_one(selector, options.dup)
|
155
|
-
end
|
156
|
-
alias :first :one
|
157
|
-
|
158
|
-
# Translate the supplied argument hash
|
159
|
-
#
|
160
|
-
# Options:
|
161
|
-
#
|
162
|
-
# criteria_conditions: Hash of criteria keys, and parameter values
|
163
|
-
#
|
164
|
-
# Example:
|
165
|
-
#
|
166
|
-
# <tt>criteria.criteria(:where => { :field => "value"}, :limit => 20)</tt>
|
167
|
-
#
|
168
|
-
# Returns <tt>self</tt>
|
169
|
-
def criteria(criteria_conditions = {})
|
170
|
-
criteria_conditions.each do |(key, value)|
|
171
|
-
send(key, value)
|
172
|
-
end
|
173
|
-
self
|
174
|
-
end
|
175
|
-
|
176
|
-
# Adds a criterion to the +Criteria+ that specifies values that must all
|
177
|
-
# be matched in order to return results. Similar to an "in" clause but the
|
178
|
-
# underlying conditional logic is an "AND" and not an "OR". The MongoDB
|
179
|
-
# conditional operator that will be used is "$all".
|
180
|
-
#
|
181
|
-
# Options:
|
182
|
-
#
|
183
|
-
# selections: A +Hash+ where the key is the field name and the value is an
|
184
|
-
# +Array+ of values that must all match.
|
185
|
-
#
|
186
|
-
# Example:
|
187
|
-
#
|
188
|
-
# <tt>criteria.every(:field => ["value1", "value2"])</tt>
|
189
|
-
#
|
190
|
-
# <tt>criteria.every(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
|
191
|
-
#
|
192
|
-
# Returns: <tt>self</tt>
|
193
|
-
def every(selections = {})
|
194
|
-
selections.each { |key, value| selector[key] = { "$all" => value } }; self
|
195
|
-
end
|
196
|
-
|
197
|
-
# Adds a criterion to the +Criteria+ that specifies values that are not allowed
|
198
|
-
# to match any document in the database. The MongoDB conditional operator that
|
199
|
-
# will be used is "$ne".
|
200
|
-
#
|
201
|
-
# Options:
|
202
|
-
#
|
203
|
-
# excludes: A +Hash+ where the key is the field name and the value is a
|
204
|
-
# value that must not be equal to the corresponding field value in the database.
|
205
|
-
#
|
206
|
-
# Example:
|
207
|
-
#
|
208
|
-
# <tt>criteria.excludes(:field => "value1")</tt>
|
209
|
-
#
|
210
|
-
# <tt>criteria.excludes(:field1 => "value1", :field2 => "value1")</tt>
|
211
|
-
#
|
212
|
-
# Returns: <tt>self</tt>
|
213
|
-
def excludes(exclusions = {})
|
214
|
-
exclusions.each { |key, value| selector[key] = { "$ne" => value } }; self
|
215
|
-
end
|
216
|
-
|
217
|
-
# Adds a criterion to the +Criteria+ that specifies additional options
|
218
|
-
# to be passed to the Ruby driver, in the exact format for the driver.
|
219
|
-
#
|
220
|
-
# Options:
|
221
|
-
#
|
222
|
-
# extras: A +Hash+ that gets set to the driver options.
|
223
|
-
#
|
224
|
-
# Example:
|
225
|
-
#
|
226
|
-
# <tt>criteria.extras(:limit => 20, :skip => 40)</tt>
|
227
|
-
#
|
228
|
-
# Returns: <tt>self</tt>
|
229
|
-
def extras(extras)
|
230
|
-
options.merge!(extras)
|
231
|
-
filter_options
|
232
|
-
self
|
233
|
-
end
|
234
|
-
|
235
|
-
# Adds a criterion to the +Criteria+ that specifies an id that must be matched.
|
236
|
-
#
|
237
|
-
# Options:
|
238
|
-
#
|
239
|
-
# id_or_object_id: A +String+ representation of a <tt>Mongo::ObjectID</tt>
|
240
|
-
#
|
241
|
-
# Example:
|
242
|
-
#
|
243
|
-
# <tt>criteria.id("4ab2bc4b8ad548971900005c")</tt>
|
244
|
-
#
|
245
|
-
# Returns: <tt>self</tt>
|
246
|
-
def id(id_or_object_id)
|
247
|
-
if id_or_object_id.kind_of?(String)
|
248
|
-
id_or_object_id = Mongo::ObjectID.from_string(id_or_object_id)
|
249
|
-
end
|
250
|
-
selector[:_id] = id_or_object_id; self
|
251
|
-
end
|
252
|
-
|
253
|
-
# Adds a criterion to the +Criteria+ that specifies values where any can
|
254
|
-
# be matched in order to return results. This is similar to an SQL "IN"
|
255
|
-
# clause. The MongoDB conditional operator that will be used is "$in".
|
256
|
-
#
|
257
|
-
# Options:
|
258
|
-
#
|
259
|
-
# inclusions: A +Hash+ where the key is the field name and the value is an
|
260
|
-
# +Array+ of values that any can match.
|
261
|
-
#
|
262
|
-
# Example:
|
263
|
-
#
|
264
|
-
# <tt>criteria.in(:field => ["value1", "value2"])</tt>
|
265
|
-
#
|
266
|
-
# <tt>criteria.in(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
|
267
|
-
#
|
268
|
-
# Returns: <tt>self</tt>
|
269
|
-
def in(inclusions = {})
|
270
|
-
inclusions.each { |key, value| selector[key] = { "$in" => value } }; self
|
271
|
-
end
|
272
|
-
|
273
|
-
# Adds a criterion to the +Criteria+ that specifies the maximum number of
|
274
|
-
# results to return. This is mostly used in conjunction with <tt>skip()</tt>
|
275
|
-
# to handle paginated results.
|
276
|
-
#
|
277
|
-
# Options:
|
278
|
-
#
|
279
|
-
# value: An +Integer+ specifying the max number of results. Defaults to 20.
|
280
|
-
#
|
281
|
-
# Example:
|
282
|
-
#
|
283
|
-
# <tt>criteria.limit(100)</tt>
|
284
|
-
#
|
285
|
-
# Returns: <tt>self</tt>
|
286
|
-
def limit(value = 20)
|
287
|
-
options[:limit] = value; self
|
288
|
-
end
|
289
|
-
|
290
|
-
# Adds a criterion to the +Criteria+ that specifies values where none
|
291
|
-
# should match in order to return results. This is similar to an SQL "NOT IN"
|
292
|
-
# clause. The MongoDB conditional operator that will be used is "$nin".
|
293
|
-
#
|
294
|
-
# Options:
|
295
|
-
#
|
296
|
-
# exclusions: A +Hash+ where the key is the field name and the value is an
|
297
|
-
# +Array+ of values that none can match.
|
298
|
-
#
|
299
|
-
# Example:
|
300
|
-
#
|
301
|
-
# <tt>criteria.not_in(:field => ["value1", "value2"])</tt>
|
302
|
-
#
|
303
|
-
# <tt>criteria.not_in(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
|
304
|
-
#
|
305
|
-
# Returns: <tt>self</tt>
|
306
|
-
def not_in(exclusions)
|
307
|
-
exclusions.each { |key, value| selector[key] = { "$nin" => value } }; self
|
308
|
-
end
|
309
|
-
|
310
|
-
# Returns the offset option. If a per_page option is in the list then it
|
311
|
-
# will replace it with a skip parameter and return the same value. Defaults
|
312
|
-
# to 20 if nothing was provided.
|
313
|
-
def offset
|
314
|
-
options[:skip]
|
315
|
-
end
|
316
|
-
|
317
|
-
# Adds a criterion to the +Criteria+ that specifies the sort order of
|
318
|
-
# the returned documents in the database. Similar to a SQL "ORDER BY".
|
319
|
-
#
|
320
|
-
# Options:
|
321
|
-
#
|
322
|
-
# params: An +Array+ of [field, direction] sorting pairs.
|
323
|
-
#
|
324
|
-
# Example:
|
325
|
-
#
|
326
|
-
# <tt>criteria.order_by([[:field1, :asc], [:field2, :desc]])</tt>
|
327
|
-
#
|
328
|
-
# Returns: <tt>self</tt>
|
329
|
-
def order_by(params = [])
|
330
|
-
options[:sort] = params; self
|
331
|
-
end
|
332
|
-
|
333
|
-
# Either returns the page option and removes it from the options, or
|
334
|
-
# returns a default value of 1.
|
335
|
-
def page
|
336
|
-
if options[:skip] && options[:limit]
|
337
|
-
(options[:skip].to_i + options[:limit].to_i) / options[:limit].to_i
|
338
|
-
else
|
339
|
-
1
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
# Executes the +Criteria+ and paginates the results.
|
344
|
-
#
|
345
|
-
# Example:
|
346
|
-
#
|
347
|
-
# <tt>criteria.paginate</tt>
|
348
|
-
def paginate
|
349
|
-
@collection ||= execute
|
350
|
-
WillPaginate::Collection.create(page, per_page, count) do |pager|
|
351
|
-
pager.replace(collection.to_a)
|
352
|
-
end
|
353
|
-
end
|
354
|
-
|
355
|
-
# Returns the number of results per page or the default of 20.
|
356
|
-
def per_page
|
357
|
-
(options[:limit] || 20).to_i
|
358
|
-
end
|
359
|
-
|
360
|
-
# Adds a criterion to the +Criteria+ that specifies the fields that will
|
361
|
-
# get returned from the Document. Used mainly for list views that do not
|
362
|
-
# require all fields to be present. This is similar to SQL "SELECT" values.
|
363
|
-
#
|
364
|
-
# Options:
|
365
|
-
#
|
366
|
-
# args: A list of field names to retrict the returned fields to.
|
367
|
-
#
|
368
|
-
# Example:
|
369
|
-
#
|
370
|
-
# <tt>criteria.only(:field1, :field2, :field3)</tt>
|
371
|
-
#
|
372
|
-
# Returns: <tt>self</tt>
|
373
|
-
def only(*args)
|
374
|
-
options[:fields] = args.flatten if args.any?; self
|
375
|
-
end
|
376
|
-
|
377
|
-
# Adds a criterion to the +Criteria+ that specifies how many results to skip
|
378
|
-
# when returning Documents. This is mostly used in conjunction with
|
379
|
-
# <tt>limit()</tt> to handle paginated results, and is similar to the
|
380
|
-
# traditional "offset" parameter.
|
381
|
-
#
|
382
|
-
# Options:
|
383
|
-
#
|
384
|
-
# value: An +Integer+ specifying the number of results to skip. Defaults to 0.
|
385
|
-
#
|
386
|
-
# Example:
|
387
|
-
#
|
388
|
-
# <tt>criteria.skip(20)</tt>
|
389
|
-
#
|
390
|
-
# Returns: <tt>self</tt>
|
391
|
-
def skip(value = 0)
|
392
|
-
options[:skip] = value; self
|
393
|
-
end
|
394
|
-
|
395
|
-
# Adds a criterion to the +Criteria+ that specifies values that must
|
396
|
-
# be matched in order to return results. This is similar to a SQL "WHERE"
|
397
|
-
# clause. This is the actual selector that will be provided to MongoDB,
|
398
|
-
# similar to the Javascript object that is used when performing a find()
|
399
|
-
# in the MongoDB console.
|
400
|
-
#
|
401
|
-
# Options:
|
402
|
-
#
|
403
|
-
# selector_or_js: A +Hash+ that must match the attributes of the +Document+
|
404
|
-
# or a +String+ of js code.
|
405
|
-
#
|
406
|
-
# Example:
|
407
|
-
#
|
408
|
-
# <tt>criteria.where(:field1 => "value1", :field2 => 15)</tt>
|
409
|
-
#
|
410
|
-
# <tt>criteria.where('this.a > 3')</tt>
|
411
|
-
#
|
412
|
-
# Returns: <tt>self</tt>
|
413
|
-
def where(selector_or_js = {})
|
414
|
-
case selector_or_js
|
415
|
-
when String
|
416
|
-
selector['$where'] = selector_or_js
|
417
|
-
else
|
418
|
-
selector.merge!(selector_or_js)
|
419
|
-
end
|
420
|
-
self
|
421
|
-
end
|
422
|
-
alias :and :where
|
423
|
-
alias :conditions :where
|
424
|
-
|
425
|
-
# Translate the supplied arguments into a +Criteria+ object.
|
426
|
-
#
|
427
|
-
# If the passed in args is a single +String+, then it will
|
428
|
-
# construct an id +Criteria+ from it.
|
429
|
-
#
|
430
|
-
# If the passed in args are a type and a hash, then it will construct
|
431
|
-
# the +Criteria+ with the proper selector, options, and type.
|
432
|
-
#
|
433
|
-
# Options:
|
434
|
-
#
|
435
|
-
# args: either a +String+ or a +Symbol+, +Hash combination.
|
436
|
-
#
|
437
|
-
# Example:
|
438
|
-
#
|
439
|
-
# <tt>Criteria.translate(Person, "4ab2bc4b8ad548971900005c")</tt>
|
440
|
-
#
|
441
|
-
# <tt>Criteria.translate(Person, :conditions => { :field => "value"}, :limit => 20)</tt>
|
442
|
-
#
|
443
|
-
# Returns a new +Criteria+ object.
|
444
|
-
def self.translate(klass, params = {})
|
445
|
-
return new(klass).id(params).one unless params.is_a?(Hash)
|
446
|
-
return new(klass).criteria(params)
|
447
|
-
end
|
448
|
-
|
449
|
-
protected
|
450
|
-
# Execute the criteria. This will take the internally built selector and options
|
451
|
-
# and pass them on to the Ruby driver's +find()+ method on the collection. The
|
452
|
-
# collection itself will be retrieved from the class provided.
|
453
|
-
#
|
454
|
-
# Returns either a cursor or an empty array.
|
455
|
-
def execute
|
456
|
-
cursor = klass.collection.find(selector, options.dup)
|
457
|
-
if cursor
|
458
|
-
@count = cursor.count
|
459
|
-
cursor
|
460
|
-
else
|
461
|
-
[]
|
462
|
-
end
|
463
|
-
end
|
464
|
-
|
465
|
-
# Filters the unused options out of the options +Hash+. Currently this
|
466
|
-
# takes into account the "page" and "per_page" options that would be passed
|
467
|
-
# in if using will_paginate.
|
468
|
-
def filter_options
|
469
|
-
page_num = options.delete(:page)
|
470
|
-
per_page_num = options.delete(:per_page)
|
471
|
-
if (page_num || per_page_num)
|
472
|
-
options[:limit] = (per_page_num || 20).to_i
|
473
|
-
options[:skip] = (page_num || 1).to_i * options[:limit] - options[:limit]
|
474
|
-
end
|
475
|
-
end
|
476
|
-
|
477
|
-
def self.invert(order)
|
478
|
-
SORT_REVERSALS[order]
|
479
|
-
end
|
1
|
+
require 'mongoid/extensions/hash/criteria_helpers'
|
2
|
+
require 'mongoid/extensions/symbol/inflections'
|
3
|
+
require 'mongodoc/matchers'
|
4
|
+
require 'mongodoc/contexts'
|
5
|
+
require 'mongoid/criteria'
|
6
|
+
|
7
|
+
module MongoDoc
|
8
|
+
module Criteria
|
9
|
+
# Create a criteria for this +Document+ class
|
10
|
+
#
|
11
|
+
# <tt>Person.criteria</tt>
|
12
|
+
def criteria
|
13
|
+
Mongoid::Criteria.new(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
delegate \
|
17
|
+
:and,
|
18
|
+
:any_in,
|
19
|
+
:cache,
|
20
|
+
:enslave,
|
21
|
+
:excludes,
|
22
|
+
:extras,
|
23
|
+
:id,
|
24
|
+
:in,
|
25
|
+
:limit,
|
26
|
+
:not_in,
|
27
|
+
:offset,
|
28
|
+
:only,
|
29
|
+
:order_by,
|
30
|
+
:page,
|
31
|
+
:per_page,
|
32
|
+
:skip,
|
33
|
+
:where, :to => :criteria
|
480
34
|
end
|
481
35
|
end
|
36
|
+
|
37
|
+
Hash.send(:include, Mongoid::Extensions::Hash::CriteriaHelpers)
|
38
|
+
Symbol.send(:include, Mongoid::Extensions::Symbol::Inflections)
|