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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1f7f42f517c065e2637b932e5119de41a5cfa9a8
4
- data.tar.gz: 98564f55e804ec82becb6c0a55de3f6170e237e9
3
+ metadata.gz: dd6f1f3f0a2cf6b1b19bdc7e0a051d15f58ddb57
4
+ data.tar.gz: 019112dbd0b6225ef841cf80a9c3dd35ce17c126
5
5
  SHA512:
6
- metadata.gz: e5d21e8cd4fc5bb3fe58753e8f5b2e5595c265580d99fe1a818c5bd2bc29ef0a16467cb4ac2324ab6e7b09669f3b2c960688f53c00752f6e29f7411ddf7aaaf4
7
- data.tar.gz: 0f1af6aac4824259221d9866379efd3f1e1dd1cc0eaad11bc532e0411e611b604b5887ce960b60513bdffc389e45f679a8df443a471a37e3926e9b5dec0e5c8c
6
+ metadata.gz: 9863446021f0b971a3d9e991bb0839a4ee511d1cf92eb57f1ac57c5efced191a45acca109cb79bbe71f80b93003cd45565d85151b3128e993c75b259fc63efe4
7
+ data.tar.gz: 249349b204180085c1491be78815c61bd3ea64ef6bc753417ffd6e9cb65dc4d679a2f871a5d19fd97d680564878097799eec6c6ceb34dc5f3af1b11e1052b79a
@@ -1,6 +1,48 @@
1
1
  # Overview
2
2
 
3
- ## 1.1.0 (branch: master)
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
@@ -167,7 +167,7 @@ module Origin
167
167
  # @since 1.0.0
168
168
  def evolve(object)
169
169
  if object.is_a?(::Array)
170
- object.map!{ |obj| obj.class.evolve(obj) }
170
+ object.map { |obj| obj.class.evolve(obj) }
171
171
  else
172
172
  object
173
173
  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.specify(value.__expand_complex__))
149
+ replacement.merge!(key.__expr_part__(value.__expand_complex__))
150
150
  end
151
151
  replacement
152
152
  end
@@ -186,7 +186,7 @@ module Origin
186
186
  return nil if object.nil?
187
187
  case object
188
188
  when ::Array
189
- object.map!{ |obj| evolve(obj) }
189
+ object.map{ |obj| evolve(obj) }
190
190
  when ::Range
191
191
  { "$gte" => evolve(object.min), "$lte" => evolve(object.max) }
192
192
  else
@@ -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".specify(value)
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 specify(value, negating = false)
62
- (negating && value.regexp?) ? { self => { "$not" => value } } : { self => value }
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.specify(value)
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 specify(value, negating = false)
20
- (negating && value.regexp?) ? { self => { "$not" => value } } : { self => value }
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.
@@ -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.specify(50)
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 specify(object, negating = false)
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 }
@@ -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.specify(value.__expand_complex__))
153
+ hash.merge!(field.__expr_part__(value.__expand_complex__))
135
154
  hash
136
155
  end
137
156
  sel.store(operator, criteria.push(normalized))
@@ -83,7 +83,11 @@ module Origin
83
83
  #
84
84
  # @since 1.0.0
85
85
  def limit(value = nil)
86
- option(value) { |options| options.store(:limit, value.to_i) }
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) { |options| options.store(:skip, value.to_i) }
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
 
@@ -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
@@ -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, @selector =
60
- Options.new(aliases, serializers), Selector.new(aliases, serializers)
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
@@ -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
- (criterion.size == 0) ? tap { |query| query.negating = true } : __override__(criterion.first, "$not")
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.specify(value.__expand_complex__, negating?))
529
+ selector.merge!(field.__expr_part__(value.__expand_complex__, negating?))
547
530
  end
548
531
  end
549
532
 
@@ -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
@@ -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
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Origin
3
- VERSION = "1.1.0"
3
+ VERSION = "2.0.0"
4
4
  end
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: 1.1.0
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-04-25 00:00:00.000000000 Z
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.0.3
74
+ rubygems_version: 2.1.11
73
75
  signing_key:
74
76
  specification_version: 4
75
77
  summary: Simple DSL for MongoDB query generation