origin 1.1.0 → 2.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -1
- data/README.md +1 -2
- data/lib/origin/aggregable.rb +116 -0
- data/lib/origin/extensions/array.rb +1 -1
- data/lib/origin/extensions/hash.rb +1 -1
- data/lib/origin/extensions/object.rb +1 -1
- data/lib/origin/extensions/string.rb +35 -3
- data/lib/origin/extensions/symbol.rb +3 -3
- data/lib/origin/key.rb +2 -2
- data/lib/origin/mergeable.rb +20 -1
- data/lib/origin/optional.rb +17 -7
- data/lib/origin/options.rb +16 -0
- data/lib/origin/pipeline.rb +107 -0
- data/lib/origin/queryable.rb +8 -2
- data/lib/origin/selectable.rb +62 -79
- data/lib/origin/selector.rb +14 -0
- data/lib/origin/smash.rb +14 -0
- data/lib/origin/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd6f1f3f0a2cf6b1b19bdc7e0a051d15f58ddb57
|
4
|
+
data.tar.gz: 019112dbd0b6225ef841cf80a9c3dd35ce17c126
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9863446021f0b971a3d9e991bb0839a4ee511d1cf92eb57f1ac57c5efced191a45acca109cb79bbe71f80b93003cd45565d85151b3128e993c75b259fc63efe4
|
7
|
+
data.tar.gz: 249349b204180085c1491be78815c61bd3ea64ef6bc753417ffd6e9cb65dc4d679a2f871a5d19fd97d680564878097799eec6c6ceb34dc5f3af1b11e1052b79a
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,48 @@
|
|
1
1
|
# Overview
|
2
2
|
|
3
|
-
##
|
3
|
+
## 2.0.0 (branch: master)
|
4
|
+
|
5
|
+
### Major Changes (Backwards Incompatible)
|
6
|
+
|
7
|
+
* \#60 Array evolution no longer modifies in place.
|
8
|
+
|
9
|
+
* Legacy geo selection has been removed, use geo_spacial now.
|
10
|
+
|
11
|
+
* \#48 `not` negation no longer ignores parameters without expressions
|
12
|
+
which follow them, but not negates them with `$ne` instead.
|
13
|
+
|
14
|
+
Previously:
|
15
|
+
|
16
|
+
selection = query.not.where(field: 1)
|
17
|
+
selection.selector # { "field" => 1 }
|
18
|
+
|
19
|
+
Now:
|
20
|
+
|
21
|
+
selection = query.not.where(field: 1)
|
22
|
+
selection.selector # { "field" => { "$ne" => 1 }}
|
23
|
+
|
24
|
+
### New Features
|
25
|
+
|
26
|
+
* \#82 Added support for $geoIntersects and $geoWithin queries via the
|
27
|
+
geo_spacial method. Examples:
|
28
|
+
|
29
|
+
query.geo_spacial(:location.intersects_line => [[ 1, 10 ], [ 2, 10 ]])
|
30
|
+
query.geo_spacial(:location.intersects_point => [[ 1, 10 ]])
|
31
|
+
query.geo_spacial(:location.intersects_polygon => [[ 1, 10 ], [ 2, 10 ], [ 1, 10 ]])
|
32
|
+
query.geo_spacial(:location.within_polygon => [[ 1, 10 ], [ 2, 10 ], [ 1, 10 ]])
|
33
|
+
|
34
|
+
### Resolved Issues
|
35
|
+
|
36
|
+
* \#83 Internal smart hashes (Smashes) now can get directly by key or alias.
|
37
|
+
(Gosha Arinich)
|
38
|
+
|
39
|
+
* \#66 Array evolution now properly coerces non arrays into arrays.
|
40
|
+
(Jared Wyatt)
|
41
|
+
|
42
|
+
* \#61 `only` and `without` can now be used together. It is up to the user
|
43
|
+
to determine when this is valid or not. (Rodrigo Saito)
|
44
|
+
|
45
|
+
## 1.1.0
|
4
46
|
|
5
47
|
### Resolved Issues
|
6
48
|
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Origin [](http://travis-ci.org/mongoid/origin)
|
1
|
+
Origin [](http://travis-ci.org/mongoid/origin) [](https://codeclimate.com/github/mongoid/origin) [](https://coveralls.io/r/mongoid/origin?branch=master)
|
2
2
|
========
|
3
3
|
|
4
4
|
Origin is a DSL for building MongoDB queries.
|
@@ -8,7 +8,6 @@ Project Tracking
|
|
8
8
|
|
9
9
|
* [Mongoid Google Group](http://groups.google.com/group/mongoid)
|
10
10
|
* [Origin Website and Documentation](http://mongoid.org/en/origin/)
|
11
|
-
* [Origin Code Climate](https://codeclimate.com/github/mongoid/origin)
|
12
11
|
|
13
12
|
Compatibility
|
14
13
|
-------------
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Origin
|
3
|
+
|
4
|
+
# Provides a DSL around crafting aggregation framework commands.
|
5
|
+
#
|
6
|
+
# @since 2.0.0
|
7
|
+
module Aggregable
|
8
|
+
extend Macroable
|
9
|
+
|
10
|
+
# @attribute [r] pipeline The aggregation pipeline.
|
11
|
+
attr_reader :pipeline
|
12
|
+
|
13
|
+
# @attribute [rw] aggregating Flag for whether or not we are aggregating.
|
14
|
+
attr_writer :aggregating
|
15
|
+
|
16
|
+
# Has the aggregable enter an aggregation state. Ie, are only aggregation
|
17
|
+
# operations allowed at this point on.
|
18
|
+
#
|
19
|
+
# @example Is the aggregable aggregating?
|
20
|
+
# aggregable.aggregating?
|
21
|
+
#
|
22
|
+
# @return [ true, false ] If the aggregable is aggregating.
|
23
|
+
#
|
24
|
+
# @since 2.0.0
|
25
|
+
def aggregating?
|
26
|
+
!!@aggregating
|
27
|
+
end
|
28
|
+
|
29
|
+
# Add a group ($group) operation to the aggregation pipeline.
|
30
|
+
#
|
31
|
+
# @example Add a group operation being verbose.
|
32
|
+
# aggregable.group(count: { "$sum" => 1 }, max: { "$max" => "likes" })
|
33
|
+
#
|
34
|
+
# @example Add a group operation using symbol shortcuts.
|
35
|
+
# aggregable.group(:count.sum => 1, :max.max => "likes")
|
36
|
+
#
|
37
|
+
# @param [ Hash ] operation The group operation.
|
38
|
+
#
|
39
|
+
# @return [ Aggregable ] The aggregable.
|
40
|
+
#
|
41
|
+
# @since 2.0.0
|
42
|
+
def group(operation)
|
43
|
+
aggregation(operation) do |pipeline|
|
44
|
+
pipeline.group(operation)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
key :avg, :override, "$avg"
|
48
|
+
key :max, :override, "$max"
|
49
|
+
key :min, :override, "$min"
|
50
|
+
key :sum, :override, "$sum"
|
51
|
+
key :last, :override, "$last"
|
52
|
+
key :push, :override, "$push"
|
53
|
+
key :first, :override, "$first"
|
54
|
+
key :add_to_set, :override, "$addToSet"
|
55
|
+
|
56
|
+
# Add a projection ($project) to the aggregation pipeline.
|
57
|
+
#
|
58
|
+
# @example Add a projection to the pipeline.
|
59
|
+
# aggregable.project(author: 1, name: 0)
|
60
|
+
#
|
61
|
+
# @param [ Hash ] criterion The projection to make.
|
62
|
+
#
|
63
|
+
# @return [ Aggregable ] The aggregable.
|
64
|
+
#
|
65
|
+
# @since 2.0.0
|
66
|
+
def project(operation = nil)
|
67
|
+
aggregation(operation) do |pipeline|
|
68
|
+
pipeline.project(operation)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Add an unwind ($unwind) to the aggregation pipeline.
|
73
|
+
#
|
74
|
+
# @example Add an unwind to the pipeline.
|
75
|
+
# aggregable.unwind(:field)
|
76
|
+
#
|
77
|
+
# @param [ String, Symbol ] field The name of the field to unwind.
|
78
|
+
#
|
79
|
+
# @return [ Aggregable ] The aggregable.
|
80
|
+
#
|
81
|
+
# @since 2.0.0
|
82
|
+
def unwind(field)
|
83
|
+
aggregation(field) do |pipeline|
|
84
|
+
pipeline.unwind(field)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
# Add the aggregation operation.
|
91
|
+
#
|
92
|
+
# @api private
|
93
|
+
#
|
94
|
+
# @example Aggregate on the operation.
|
95
|
+
# aggregation(operation) do |pipeline|
|
96
|
+
# pipeline.push("$project" => operation)
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# @param [ Hash ] operation The operation for the pipeline.
|
100
|
+
#
|
101
|
+
# @return [ Aggregable ] The cloned aggregable.
|
102
|
+
#
|
103
|
+
# @since 2.0.0
|
104
|
+
def aggregation(operation)
|
105
|
+
return self unless operation
|
106
|
+
clone.tap do |query|
|
107
|
+
unless aggregating?
|
108
|
+
query.pipeline.concat(query.selector.to_pipeline)
|
109
|
+
query.pipeline.concat(query.options.to_pipeline)
|
110
|
+
query.aggregating = true
|
111
|
+
end
|
112
|
+
yield(query.pipeline)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -146,7 +146,7 @@ module Origin
|
|
146
146
|
def __expand_complex__
|
147
147
|
replacement = {}
|
148
148
|
each_pair do |key, value|
|
149
|
-
replacement.merge!(key.
|
149
|
+
replacement.merge!(key.__expr_part__(value.__expand_complex__))
|
150
150
|
end
|
151
151
|
replacement
|
152
152
|
end
|
@@ -30,6 +30,18 @@ module Origin
|
|
30
30
|
::Time.parse(self).utc
|
31
31
|
end
|
32
32
|
|
33
|
+
# Get the string as a mongo expression, adding $ to the front.
|
34
|
+
#
|
35
|
+
# @example Get the string as an expression.
|
36
|
+
# "test".__mongo_expression__
|
37
|
+
#
|
38
|
+
# @return [ String ] The string with $ at the front.
|
39
|
+
#
|
40
|
+
# @since 2.0.0
|
41
|
+
def __mongo_expression__
|
42
|
+
start_with?("$") ? self : "$#{self}"
|
43
|
+
end
|
44
|
+
|
33
45
|
# Get the string as a sort option.
|
34
46
|
#
|
35
47
|
# @example Get the string as a sort option.
|
@@ -50,7 +62,7 @@ module Origin
|
|
50
62
|
# Get the string as a specification.
|
51
63
|
#
|
52
64
|
# @example Get the string as a criteria.
|
53
|
-
# "field".
|
65
|
+
# "field".__expr_part__(value)
|
54
66
|
#
|
55
67
|
# @param [ Object ] value The value of the criteria.
|
56
68
|
# @param [ true, false ] negating If the selection should be negated.
|
@@ -58,8 +70,8 @@ module Origin
|
|
58
70
|
# @return [ Hash ] The selection.
|
59
71
|
#
|
60
72
|
# @since 1.0.0
|
61
|
-
def
|
62
|
-
(
|
73
|
+
def __expr_part__(value, negating = false)
|
74
|
+
::String.__expr_part__(self, value, negating)
|
63
75
|
end
|
64
76
|
|
65
77
|
# Get the string as a sort direction.
|
@@ -76,6 +88,26 @@ module Origin
|
|
76
88
|
|
77
89
|
module ClassMethods
|
78
90
|
|
91
|
+
# Get the value as a expression.
|
92
|
+
#
|
93
|
+
# @example Get the value as an expression.
|
94
|
+
# String.__expr_part__("field", value)
|
95
|
+
#
|
96
|
+
# @param [ String, Symbol ] key The field key.
|
97
|
+
# @param [ Object ] value The value of the criteria.
|
98
|
+
# @param [ true, false ] negating If the selection should be negated.
|
99
|
+
#
|
100
|
+
# @return [ Hash ] The selection.
|
101
|
+
#
|
102
|
+
# @since 2.0.0
|
103
|
+
def __expr_part__(key, value, negating = false)
|
104
|
+
if negating
|
105
|
+
{ key => { "$#{value.regexp? ? "not" : "ne"}" => value }}
|
106
|
+
else
|
107
|
+
{ key => value }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
79
111
|
# Evolves the string into a MongoDB friendly value - in this case
|
80
112
|
# a string.
|
81
113
|
#
|
@@ -8,7 +8,7 @@ module Origin
|
|
8
8
|
# Get the symbol as a specification.
|
9
9
|
#
|
10
10
|
# @example Get the symbol as a criteria.
|
11
|
-
# :field.
|
11
|
+
# :field.__expr_part__(value)
|
12
12
|
#
|
13
13
|
# @param [ Object ] value The value of the criteria.
|
14
14
|
# @param [ true, false ] negating If the selection should be negated.
|
@@ -16,8 +16,8 @@ module Origin
|
|
16
16
|
# @return [ Hash ] The selection.
|
17
17
|
#
|
18
18
|
# @since 1.0.0
|
19
|
-
def
|
20
|
-
(
|
19
|
+
def __expr_part__(value, negating = false)
|
20
|
+
::String.__expr_part__(self, value, negating)
|
21
21
|
end
|
22
22
|
|
23
23
|
# Get the symbol as a sort direction.
|
data/lib/origin/key.rb
CHANGED
@@ -57,7 +57,7 @@ module Origin
|
|
57
57
|
# Gets the raw selector that would be passed to Mongo from this key.
|
58
58
|
#
|
59
59
|
# @example Specify the raw selector.
|
60
|
-
# key.
|
60
|
+
# key.__expr_part__(50)
|
61
61
|
#
|
62
62
|
# @param [ Object ] object The value to be included.
|
63
63
|
# @param [ true, false ] negating If the selection should be negated.
|
@@ -65,7 +65,7 @@ module Origin
|
|
65
65
|
# @return [ Hash ] The raw MongoDB selector.
|
66
66
|
#
|
67
67
|
# @since 1.0.0
|
68
|
-
def
|
68
|
+
def __expr_part__(object, negating = false)
|
69
69
|
value = block ? block[object] : object
|
70
70
|
expression = { operator => expanded ? { expanded => value } : value }
|
71
71
|
{ name.to_s => (negating && operator != "$not") ? { "$not" => expression } : expression }
|
data/lib/origin/mergeable.rb
CHANGED
@@ -94,6 +94,25 @@ module Origin
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
+
# Perform a straight merge of the criterion into the selection and let the
|
98
|
+
# symbol overrides do all the work.
|
99
|
+
#
|
100
|
+
# @api private
|
101
|
+
#
|
102
|
+
# @example Straight merge the expanded criterion.
|
103
|
+
# mergeable.__merge__(location: [ 1, 10 ])
|
104
|
+
#
|
105
|
+
# @param [ Hash ] criterion The criteria.
|
106
|
+
#
|
107
|
+
# @return [ Mergeable ] The cloned object.
|
108
|
+
#
|
109
|
+
# @since 2.0.0
|
110
|
+
def __merge__(criterion)
|
111
|
+
selection(criterion) do |selector, field, value|
|
112
|
+
selector.merge!(field.__expr_part__(value))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
97
116
|
# Adds the criterion to the existing selection.
|
98
117
|
#
|
99
118
|
# @api private
|
@@ -131,7 +150,7 @@ module Origin
|
|
131
150
|
next unless expr
|
132
151
|
criteria = sel[operator] || []
|
133
152
|
normalized = expr.inject({}) do |hash, (field, value)|
|
134
|
-
hash.merge!(field.
|
153
|
+
hash.merge!(field.__expr_part__(value.__expand_complex__))
|
135
154
|
hash
|
136
155
|
end
|
137
156
|
sel.store(operator, criteria.push(normalized))
|
data/lib/origin/optional.rb
CHANGED
@@ -83,7 +83,11 @@ module Origin
|
|
83
83
|
#
|
84
84
|
# @since 1.0.0
|
85
85
|
def limit(value = nil)
|
86
|
-
option(value)
|
86
|
+
option(value) do |options, query|
|
87
|
+
val = value.to_i
|
88
|
+
options.store(:limit, val)
|
89
|
+
query.pipeline.push("$limit" => val) if aggregating?
|
90
|
+
end
|
87
91
|
end
|
88
92
|
|
89
93
|
# Adds the option to limit the number of documents scanned in the
|
@@ -127,7 +131,7 @@ module Origin
|
|
127
131
|
args = args.flatten
|
128
132
|
option(*args) do |options|
|
129
133
|
options.store(
|
130
|
-
:fields, args.inject({}){ |sub, field| sub.tap { sub[field] = 1 }}
|
134
|
+
:fields, args.inject(options[:fields] || {}){ |sub, field| sub.tap { sub[field] = 1 }}
|
131
135
|
)
|
132
136
|
end
|
133
137
|
end
|
@@ -164,11 +168,12 @@ module Origin
|
|
164
168
|
#
|
165
169
|
# @since 1.0.0
|
166
170
|
def order_by(*spec)
|
167
|
-
option(spec) do |options|
|
171
|
+
option(spec) do |options, query|
|
168
172
|
spec.compact.each do |criterion|
|
169
173
|
criterion.__sort_option__.each_pair do |field, direction|
|
170
174
|
add_sort_option(options, field, direction)
|
171
175
|
end
|
176
|
+
query.pipeline.push("$sort" => options[:sort]) if aggregating?
|
172
177
|
end
|
173
178
|
end
|
174
179
|
end
|
@@ -184,7 +189,11 @@ module Origin
|
|
184
189
|
#
|
185
190
|
# @since 1.0.0
|
186
191
|
def skip(value = nil)
|
187
|
-
option(value)
|
192
|
+
option(value) do |options, query|
|
193
|
+
val = value.to_i
|
194
|
+
options.store(:skip, val)
|
195
|
+
query.pipeline.push("$skip" => val) if aggregating?
|
196
|
+
end
|
188
197
|
end
|
189
198
|
alias :offset :skip
|
190
199
|
|
@@ -236,7 +245,7 @@ module Origin
|
|
236
245
|
args = args.flatten
|
237
246
|
option(*args) do |options|
|
238
247
|
options.store(
|
239
|
-
:fields, args.inject({}){ |sub, field| sub.tap { sub[field] = 0 }}
|
248
|
+
:fields, args.inject(options[:fields] || {}){ |sub, field| sub.tap { sub[field] = 0 }}
|
240
249
|
)
|
241
250
|
end
|
242
251
|
end
|
@@ -285,7 +294,7 @@ module Origin
|
|
285
294
|
def option(*args)
|
286
295
|
clone.tap do |query|
|
287
296
|
unless args.compact.empty?
|
288
|
-
yield(query.options)
|
297
|
+
yield(query.options, query)
|
289
298
|
end
|
290
299
|
end
|
291
300
|
end
|
@@ -304,10 +313,11 @@ module Origin
|
|
304
313
|
#
|
305
314
|
# @since 1.0.0
|
306
315
|
def sort_with_list(*fields, direction)
|
307
|
-
option(fields) do |options|
|
316
|
+
option(fields) do |options, query|
|
308
317
|
fields.flatten.compact.each do |field|
|
309
318
|
add_sort_option(options, field, direction)
|
310
319
|
end
|
320
|
+
query.pipeline.push("$sort" => options[:sort]) if aggregating?
|
311
321
|
end
|
312
322
|
end
|
313
323
|
|
data/lib/origin/options.rb
CHANGED
@@ -70,6 +70,22 @@ module Origin
|
|
70
70
|
end
|
71
71
|
alias :[]= :store
|
72
72
|
|
73
|
+
# Convert the options to aggregation pipeline friendly options.
|
74
|
+
#
|
75
|
+
# @example Convert the options to a pipeline.
|
76
|
+
# options.to_pipeline
|
77
|
+
#
|
78
|
+
# @return [ Array<Hash> ] The options in pipeline form.
|
79
|
+
#
|
80
|
+
# @since 2.0.0
|
81
|
+
def to_pipeline
|
82
|
+
pipeline = []
|
83
|
+
pipeline.push({ "$skip" => skip }) if skip
|
84
|
+
pipeline.push({ "$limit" => limit }) if limit
|
85
|
+
pipeline.push({ "$sort" => sort }) if sort
|
86
|
+
pipeline
|
87
|
+
end
|
88
|
+
|
73
89
|
private
|
74
90
|
|
75
91
|
# Evolve a single key selection with various types of values.
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Origin
|
3
|
+
|
4
|
+
# Represents an aggregation pipeline.
|
5
|
+
#
|
6
|
+
# @since 2.0.0
|
7
|
+
class Pipeline < Array
|
8
|
+
|
9
|
+
# @attribute [r] aliases The field aliases.
|
10
|
+
attr_reader :aliases
|
11
|
+
|
12
|
+
# Deep copy the aggregation pipeline. Will clone all the values in the
|
13
|
+
# pipeline as well as the pipeline itself.
|
14
|
+
#
|
15
|
+
# @example Deep copy the pipeline.
|
16
|
+
# pipeline.__deep_copy__
|
17
|
+
#
|
18
|
+
# @return [ Pipeline ] The cloned pipeline.
|
19
|
+
#
|
20
|
+
# @since 2.0.0
|
21
|
+
def __deep_copy__
|
22
|
+
self.class.new(aliases) do |copy|
|
23
|
+
each do |entry|
|
24
|
+
copy.push(entry.__deep_copy__)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Add a group operation to the aggregation pipeline.
|
30
|
+
#
|
31
|
+
# @example Add a group operation.
|
32
|
+
# pipeline.group(:count.sum => 1, :max.max => "likes")
|
33
|
+
#
|
34
|
+
# @param [ Hash ] entry The group entry.
|
35
|
+
#
|
36
|
+
# @return [ Pipeline ] The pipeline.
|
37
|
+
#
|
38
|
+
# @since 2.0.0
|
39
|
+
def group(entry)
|
40
|
+
push("$group" => evolve(entry.__expand_complex__))
|
41
|
+
end
|
42
|
+
|
43
|
+
# Initialize the new pipeline.
|
44
|
+
#
|
45
|
+
# @example Initialize the new pipeline.
|
46
|
+
# Origin::Pipeline.new(aliases)
|
47
|
+
#
|
48
|
+
# @param [ Hash ] aliases A hash of mappings from aliases to the actual
|
49
|
+
# field names in the database.
|
50
|
+
#
|
51
|
+
# @since 2.0.0
|
52
|
+
def initialize(aliases = {})
|
53
|
+
@aliases = aliases
|
54
|
+
yield(self) if block_given?
|
55
|
+
end
|
56
|
+
|
57
|
+
# Adds a $project entry to the aggregation pipeline.
|
58
|
+
#
|
59
|
+
# @example Add the projection.
|
60
|
+
# pipeline.project(name: 1)
|
61
|
+
#
|
62
|
+
# @param [ Hash ] entry The projection.
|
63
|
+
#
|
64
|
+
# @return [ Pipeline ] The pipeline.
|
65
|
+
def project(entry)
|
66
|
+
push("$project" => evolve(entry))
|
67
|
+
end
|
68
|
+
|
69
|
+
# Add the $unwind entry to the pipeline.
|
70
|
+
#
|
71
|
+
# @example Add the unwind.
|
72
|
+
# pipeline.unwind(:field)
|
73
|
+
#
|
74
|
+
# @param [ String, Symbol ] field The name of the field.
|
75
|
+
#
|
76
|
+
# @return [ Pipeline ] The pipeline.
|
77
|
+
#
|
78
|
+
# @since 2.0.0
|
79
|
+
def unwind(field)
|
80
|
+
normalized = field.to_s
|
81
|
+
name = aliases[normalized] || normalized
|
82
|
+
push("$unwind" => name.__mongo_expression__)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Evolve the entry using the aliases.
|
88
|
+
#
|
89
|
+
# @api private
|
90
|
+
#
|
91
|
+
# @example Evolve the entry.
|
92
|
+
# pipeline.evolve(name: 1)
|
93
|
+
#
|
94
|
+
# @param [ Hash ] entry The entry to evolve.
|
95
|
+
#
|
96
|
+
# @return [ Hash ] The evolved entry.
|
97
|
+
#
|
98
|
+
# @since 2.0.0
|
99
|
+
def evolve(entry)
|
100
|
+
aggregate = Selector.new(aliases)
|
101
|
+
entry.each_pair do |field, value|
|
102
|
+
aggregate.merge!(field.to_s => value)
|
103
|
+
end
|
104
|
+
aggregate
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/origin/queryable.rb
CHANGED
@@ -4,6 +4,8 @@ require "origin/key"
|
|
4
4
|
require "origin/macroable"
|
5
5
|
require "origin/mergeable"
|
6
6
|
require "origin/smash"
|
7
|
+
require "origin/aggregable"
|
8
|
+
require "origin/pipeline"
|
7
9
|
require "origin/optional"
|
8
10
|
require "origin/options"
|
9
11
|
require "origin/selectable"
|
@@ -19,6 +21,8 @@ module Origin
|
|
19
21
|
# include Origin::Queryable
|
20
22
|
# end
|
21
23
|
module Queryable
|
24
|
+
include Mergeable
|
25
|
+
include Aggregable
|
22
26
|
include Selectable
|
23
27
|
include Optional
|
24
28
|
|
@@ -56,8 +60,9 @@ module Origin
|
|
56
60
|
# @since 1.0.0
|
57
61
|
def initialize(aliases = {}, serializers = {}, driver = :moped)
|
58
62
|
@aliases, @driver, @serializers = aliases, driver.to_sym, serializers
|
59
|
-
@options,
|
60
|
-
|
63
|
+
@options = Options.new(aliases, serializers)
|
64
|
+
@selector = Selector.new(aliases, serializers)
|
65
|
+
@pipeline = Pipeline.new(aliases)
|
61
66
|
yield(self) if block_given?
|
62
67
|
end
|
63
68
|
|
@@ -72,6 +77,7 @@ module Origin
|
|
72
77
|
def initialize_copy(other)
|
73
78
|
@options = other.options.__deep_copy__
|
74
79
|
@selector = other.selector.__deep_copy__
|
80
|
+
@pipeline = other.pipeline.__deep_copy__
|
75
81
|
end
|
76
82
|
end
|
77
83
|
end
|
data/lib/origin/selectable.rb
CHANGED
@@ -5,9 +5,23 @@ module Origin
|
|
5
5
|
# document from the database. The selectable module brings all functionality
|
6
6
|
# to the selectable that has to do with building MongoDB selectors.
|
7
7
|
module Selectable
|
8
|
-
include Mergeable
|
9
8
|
extend Macroable
|
10
9
|
|
10
|
+
# Constant for a LineString $geometry.
|
11
|
+
#
|
12
|
+
# @since 2.0.0
|
13
|
+
LINE_STRING = "LineString"
|
14
|
+
|
15
|
+
# Constant for a Point $geometry.
|
16
|
+
#
|
17
|
+
# @since 2.0.0
|
18
|
+
POINT = "Point"
|
19
|
+
|
20
|
+
# Constant for a Polygon $geometry.
|
21
|
+
#
|
22
|
+
# @since 2.0.0
|
23
|
+
POLYGON = "Polygon"
|
24
|
+
|
11
25
|
# @attribute [rw] negating If the next spression is negated.
|
12
26
|
# @attribute [rw] selector The query selector.
|
13
27
|
attr_accessor :negating, :selector
|
@@ -118,6 +132,47 @@ module Origin
|
|
118
132
|
::Boolean.evolve(value)
|
119
133
|
end
|
120
134
|
|
135
|
+
# Add a $geoIntersects or $geoWithin selection. Symbol operators must be used as shown in
|
136
|
+
# the examples to expand the criteria.
|
137
|
+
#
|
138
|
+
# @note The only valid geometry shapes for a $geoIntersects are:
|
139
|
+
# :intersects_line, :intersects_point, and :intersects_polygon.
|
140
|
+
#
|
141
|
+
# @note The only valid geometry shape for a $geoWithin is :within_polygon
|
142
|
+
#
|
143
|
+
# @example Add a geo intersect criterion for a line.
|
144
|
+
# query.geo_spacial(:location.intersects_line => [[ 1, 10 ], [ 2, 10 ]])
|
145
|
+
#
|
146
|
+
# @example Add a geo intersect criterion for a point.
|
147
|
+
# query.geo_spacial(:location.intersects_point => [[ 1, 10 ]])
|
148
|
+
#
|
149
|
+
# @example Add a geo intersect criterion for a polygon.
|
150
|
+
# query.geo_spacial(:location.intersects_polygon => [[ 1, 10 ], [ 2, 10 ], [ 1, 10 ]])
|
151
|
+
#
|
152
|
+
# @example Add a geo within criterion for a polygon.
|
153
|
+
# query.geo_spacial(:location.within_polygon => [[ 1, 10 ], [ 2, 10 ], [ 1, 10 ]])
|
154
|
+
#
|
155
|
+
# @param [ Hash ] criterion The criterion.
|
156
|
+
#
|
157
|
+
# @return [ Selectable ] The cloned selectable.
|
158
|
+
#
|
159
|
+
# @since 2.0.0
|
160
|
+
def geo_spacial(criterion = nil)
|
161
|
+
__merge__(criterion)
|
162
|
+
end
|
163
|
+
key :intersects_line, :override, "$geoIntersects", "$geometry" do |value|
|
164
|
+
{ "type" => LINE_STRING, "coordinates" => value }
|
165
|
+
end
|
166
|
+
key :intersects_point, :override, "$geoIntersects", "$geometry" do |value|
|
167
|
+
{ "type" => POINT, "coordinates" => value }
|
168
|
+
end
|
169
|
+
key :intersects_polygon, :override, "$geoIntersects", "$geometry" do |value|
|
170
|
+
{ "type" => POLYGON, "coordinates" => value }
|
171
|
+
end
|
172
|
+
key :within_polygon, :override, "$geoWithin", "$geometry" do |value|
|
173
|
+
{ "type" => POLYGON, "coordinates" => value }
|
174
|
+
end
|
175
|
+
|
121
176
|
# Add the $gt criterion to the selector.
|
122
177
|
#
|
123
178
|
# @example Add the $gt criterion.
|
@@ -364,7 +419,11 @@ module Origin
|
|
364
419
|
#
|
365
420
|
# @since 1.0.0
|
366
421
|
def not(*criterion)
|
367
|
-
|
422
|
+
if criterion.empty?
|
423
|
+
tap { |query| query.negating = true }
|
424
|
+
else
|
425
|
+
__override__(criterion.first, "$not")
|
426
|
+
end
|
368
427
|
end
|
369
428
|
key :not, :override, "$not"
|
370
429
|
|
@@ -451,82 +510,6 @@ module Origin
|
|
451
510
|
criterion.is_a?(String) ? js_query(criterion) : expr_query(criterion)
|
452
511
|
end
|
453
512
|
|
454
|
-
# Adds the $within/$box selection to the selectable.
|
455
|
-
#
|
456
|
-
# @example Add the selection.
|
457
|
-
# selectable.within_box(location: [[ 1, 10 ], [ 10, 1 ]])
|
458
|
-
#
|
459
|
-
# @example Execute an $within/$box in a where query.
|
460
|
-
# selectable.where(:field.within_box => [[ 1, 10 ], [ 10, 1 ]])
|
461
|
-
#
|
462
|
-
# @param [ Hash ] criterion The field/box corner criterion.
|
463
|
-
#
|
464
|
-
# @return [ Selectable ] The cloned selectable.
|
465
|
-
#
|
466
|
-
# @since 1.0.0
|
467
|
-
def within_box(criterion = nil)
|
468
|
-
__expanded__(criterion, "$within", "$box")
|
469
|
-
end
|
470
|
-
key :within_box, :expanded, "$within", "$box"
|
471
|
-
|
472
|
-
# Adds the $within/$center selection to the selectable.
|
473
|
-
#
|
474
|
-
# @example Add the selection.
|
475
|
-
# selectable.within_circle(location: [[ 1, 10 ], 25 ])
|
476
|
-
#
|
477
|
-
# @example Execute an $within/$center in a where query.
|
478
|
-
# selectable.where(:field.within_circle => [[ 1, 10 ], 25 ])
|
479
|
-
#
|
480
|
-
# @param [ Hash ] criterion The field/radius criterion.
|
481
|
-
#
|
482
|
-
# @return [ Selectable ] The cloned selectable.
|
483
|
-
#
|
484
|
-
# @since 1.0.0
|
485
|
-
def within_circle(criterion = nil)
|
486
|
-
__expanded__(criterion, "$within", "$center")
|
487
|
-
end
|
488
|
-
key :within_circle, :expanded, "$within", "$center"
|
489
|
-
|
490
|
-
# Adds the $within/$polygon selection to the selectable.
|
491
|
-
#
|
492
|
-
# @example Add the selection.
|
493
|
-
# selectable.within_polygon(
|
494
|
-
# location: [[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]
|
495
|
-
# )
|
496
|
-
#
|
497
|
-
# @example Execute an $within/$polygon in a where query.
|
498
|
-
# selectable.where(
|
499
|
-
# :field.within_polygon => [[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]
|
500
|
-
# )
|
501
|
-
#
|
502
|
-
# @param [ Hash ] criterion The field/polygon points criterion.
|
503
|
-
#
|
504
|
-
# @return [ Selectable ] The cloned selectable.
|
505
|
-
#
|
506
|
-
# @since 1.0.0
|
507
|
-
def within_polygon(criterion = nil)
|
508
|
-
__expanded__(criterion, "$within", "$polygon")
|
509
|
-
end
|
510
|
-
key :within_polygon, :expanded, "$within", "$polygon"
|
511
|
-
|
512
|
-
# Adds the $within/$centerSphere selection to the selectable.
|
513
|
-
#
|
514
|
-
# @example Add the selection.
|
515
|
-
# selectable.within_spherical_circle(location: [[ 1, 10 ], 25 ])
|
516
|
-
#
|
517
|
-
# @example Execute an $within/$centerSphere in a where query.
|
518
|
-
# selectable.where(:field.within_spherical_circle => [[ 1, 10 ], 25 ])
|
519
|
-
#
|
520
|
-
# @param [ Hash ] criterion The field/distance criterion.
|
521
|
-
#
|
522
|
-
# @return [ Selectable ] The cloned selectable.
|
523
|
-
#
|
524
|
-
# @since 1.0.0
|
525
|
-
def within_spherical_circle(criterion = nil)
|
526
|
-
__expanded__(criterion, "$within", "$centerSphere")
|
527
|
-
end
|
528
|
-
key :within_spherical_circle, :expanded, "$within", "$centerSphere"
|
529
|
-
|
530
513
|
private
|
531
514
|
|
532
515
|
# Create the standard expression query.
|
@@ -543,7 +526,7 @@ module Origin
|
|
543
526
|
# @since 1.0.0
|
544
527
|
def expr_query(criterion)
|
545
528
|
selection(criterion) do |selector, field, value|
|
546
|
-
selector.merge!(field.
|
529
|
+
selector.merge!(field.__expr_part__(value.__expand_complex__, negating?))
|
547
530
|
end
|
548
531
|
end
|
549
532
|
|
data/lib/origin/selector.rb
CHANGED
@@ -49,6 +49,20 @@ module Origin
|
|
49
49
|
end
|
50
50
|
alias :[]= :store
|
51
51
|
|
52
|
+
# Convert the selector to an aggregation pipeline entry.
|
53
|
+
#
|
54
|
+
# @example Convert the selector to a pipeline.
|
55
|
+
# selector.to_pipeline
|
56
|
+
#
|
57
|
+
# @return [ Array<Hash> ] The pipeline entry for the selector.
|
58
|
+
#
|
59
|
+
# @since 2.0.0
|
60
|
+
def to_pipeline
|
61
|
+
pipeline = []
|
62
|
+
pipeline.push({ "$match" => self }) unless empty?
|
63
|
+
pipeline
|
64
|
+
end
|
65
|
+
|
52
66
|
private
|
53
67
|
|
54
68
|
# Evolves a multi-list selection, like an $and or $or criterion, and
|
data/lib/origin/smash.rb
CHANGED
@@ -42,6 +42,20 @@ module Origin
|
|
42
42
|
yield(self) if block_given?
|
43
43
|
end
|
44
44
|
|
45
|
+
# Get an item from the smart hash by the provided key.
|
46
|
+
#
|
47
|
+
# @example Get an item by the key.
|
48
|
+
# smash["test"]
|
49
|
+
#
|
50
|
+
# @param [ String ] key The key.
|
51
|
+
#
|
52
|
+
# @return [ Object ] The found object.
|
53
|
+
#
|
54
|
+
# @since 2.0.0
|
55
|
+
def [](key)
|
56
|
+
fetch(aliases[key]) { super }
|
57
|
+
end
|
58
|
+
|
45
59
|
private
|
46
60
|
|
47
61
|
# Get the normalized value for the key. If localization is in play the
|
data/lib/origin/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: origin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Durran Jordan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-12-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Origin is a simple DSL for generating MongoDB selectors and options
|
14
14
|
email:
|
@@ -17,6 +17,7 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
+
- lib/origin/aggregable.rb
|
20
21
|
- lib/origin/extensions/array.rb
|
21
22
|
- lib/origin/extensions/big_decimal.rb
|
22
23
|
- lib/origin/extensions/boolean.rb
|
@@ -40,6 +41,7 @@ files:
|
|
40
41
|
- lib/origin/mergeable.rb
|
41
42
|
- lib/origin/optional.rb
|
42
43
|
- lib/origin/options.rb
|
44
|
+
- lib/origin/pipeline.rb
|
43
45
|
- lib/origin/queryable.rb
|
44
46
|
- lib/origin/selectable.rb
|
45
47
|
- lib/origin/selector.rb
|
@@ -69,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
71
|
version: 1.3.6
|
70
72
|
requirements: []
|
71
73
|
rubyforge_project: origin
|
72
|
-
rubygems_version: 2.
|
74
|
+
rubygems_version: 2.1.11
|
73
75
|
signing_key:
|
74
76
|
specification_version: 4
|
75
77
|
summary: Simple DSL for MongoDB query generation
|