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 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