origin 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://secure.travis-ci.org/mongoid/origin.png?branch=master&.png)](http://travis-ci.org/mongoid/origin)
|
1
|
+
Origin [![Build Status](https://secure.travis-ci.org/mongoid/origin.png?branch=master&.png)](http://travis-ci.org/mongoid/origin) [![Code Climate](https://codeclimate.com/github/mongoid/origin.png)](https://codeclimate.com/github/mongoid/origin) [![Coverage Status](https://coveralls.io/repos/mongoid/origin/badge.png?branch=master)](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
|