origin 0.0.0.alpha → 1.0.0.alpha

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.
Files changed (83) hide show
  1. data/Rakefile +3 -0
  2. data/lib/origin.rb +3 -4
  3. data/lib/origin/extensions.rb +25 -0
  4. data/lib/origin/extensions/array.rb +153 -0
  5. data/lib/origin/extensions/big_decimal.rb +33 -0
  6. data/lib/origin/extensions/boolean.rb +30 -0
  7. data/lib/origin/extensions/date.rb +59 -0
  8. data/lib/origin/extensions/date_time.rb +44 -0
  9. data/lib/origin/extensions/hash.rb +180 -0
  10. data/lib/origin/extensions/nil_class.rb +82 -0
  11. data/lib/origin/extensions/numeric.rb +86 -0
  12. data/lib/origin/extensions/object.rb +182 -0
  13. data/lib/origin/extensions/range.rb +66 -0
  14. data/lib/origin/extensions/regexp.rb +41 -0
  15. data/lib/origin/extensions/set.rb +28 -0
  16. data/lib/origin/extensions/string.rb +100 -0
  17. data/lib/origin/extensions/symbol.rb +74 -0
  18. data/lib/origin/extensions/time.rb +44 -0
  19. data/lib/origin/extensions/time_with_zone.rb +50 -0
  20. data/lib/origin/forwardable.rb +57 -0
  21. data/lib/origin/key.rb +74 -0
  22. data/lib/origin/macroable.rb +23 -0
  23. data/lib/origin/mergeable.rb +226 -0
  24. data/lib/origin/optional.rb +314 -29
  25. data/lib/origin/options.rb +64 -1
  26. data/lib/origin/queryable.rb +55 -12
  27. data/lib/origin/selectable.rb +613 -0
  28. data/lib/origin/selector.rb +140 -1
  29. data/lib/origin/smash.rb +85 -0
  30. data/lib/origin/version.rb +1 -1
  31. metadata +94 -62
  32. data/lib/origin/ext.rb +0 -5
  33. data/lib/origin/ext/array.rb +0 -21
  34. data/lib/origin/ext/hash.rb +0 -38
  35. data/lib/origin/ext/nil.rb +0 -9
  36. data/lib/origin/ext/object.rb +0 -25
  37. data/lib/origin/optional/batch_size.rb +0 -11
  38. data/lib/origin/optional/hint.rb +0 -15
  39. data/lib/origin/optional/limit.rb +0 -11
  40. data/lib/origin/optional/max_scan.rb +0 -11
  41. data/lib/origin/optional/no_timeout.rb +0 -11
  42. data/lib/origin/optional/only.rb +0 -15
  43. data/lib/origin/optional/read.rb +0 -11
  44. data/lib/origin/optional/return_key.rb +0 -11
  45. data/lib/origin/optional/show_disk_loc.rb +0 -11
  46. data/lib/origin/optional/skip.rb +0 -11
  47. data/lib/origin/optional/slice.rb +0 -17
  48. data/lib/origin/optional/snapshot.rb +0 -13
  49. data/lib/origin/optional/transformer.rb +0 -11
  50. data/lib/origin/optional/without.rb +0 -15
  51. data/lib/origin/selection.rb +0 -59
  52. data/lib/origin/selection/all.rb +0 -18
  53. data/lib/origin/selection/and.rb +0 -11
  54. data/lib/origin/selection/between.rb +0 -16
  55. data/lib/origin/selection/elem_match.rb +0 -18
  56. data/lib/origin/selection/exists.rb +0 -18
  57. data/lib/origin/selection/gt.rb +0 -18
  58. data/lib/origin/selection/gte.rb +0 -18
  59. data/lib/origin/selection/in.rb +0 -18
  60. data/lib/origin/selection/key.rb +0 -20
  61. data/lib/origin/selection/lt.rb +0 -18
  62. data/lib/origin/selection/lte.rb +0 -18
  63. data/lib/origin/selection/max_distance.rb +0 -11
  64. data/lib/origin/selection/mod.rb +0 -18
  65. data/lib/origin/selection/ne.rb +0 -18
  66. data/lib/origin/selection/near.rb +0 -18
  67. data/lib/origin/selection/near_sphere.rb +0 -18
  68. data/lib/origin/selection/nin.rb +0 -18
  69. data/lib/origin/selection/nor.rb +0 -11
  70. data/lib/origin/selection/or.rb +0 -11
  71. data/lib/origin/selection/size.rb +0 -18
  72. data/lib/origin/selection/strategies.rb +0 -40
  73. data/lib/origin/selection/strategies/add.rb +0 -18
  74. data/lib/origin/selection/strategies/expanded.rb +0 -15
  75. data/lib/origin/selection/strategies/intersect.rb +0 -22
  76. data/lib/origin/selection/strategies/multi.rb +0 -21
  77. data/lib/origin/selection/strategies/override.rb +0 -19
  78. data/lib/origin/selection/strategies/union.rb +0 -22
  79. data/lib/origin/selection/type.rb +0 -18
  80. data/lib/origin/selection/where.rb +0 -29
  81. data/lib/origin/selection/within_box.rb +0 -18
  82. data/lib/origin/selection/within_circle.rb +0 -18
  83. data/lib/origin/selection/within_spherical_circle.rb +0 -18
@@ -0,0 +1,226 @@
1
+ # encoding: utf-8
2
+ module Origin
3
+
4
+ # Contains behaviour for merging existing selection with new selection.
5
+ module Mergeable
6
+
7
+ # @attribute [rw] strategy The name of the current strategy.
8
+ attr_accessor :strategy
9
+
10
+ # Instruct the next mergeable call to use intersection.
11
+ #
12
+ # @example Use intersection on the next call.
13
+ # mergeable.intersect.in(field: [ 1, 2, 3 ])
14
+ #
15
+ # @return [ Mergeable ] The intersect flagged mergeable.
16
+ #
17
+ # @since 1.0.0
18
+ def intersect
19
+ use(:__intersect__)
20
+ end
21
+
22
+ # Instruct the next mergeable call to use override.
23
+ #
24
+ # @example Use override on the next call.
25
+ # mergeable.override.in(field: [ 1, 2, 3 ])
26
+ #
27
+ # @return [ Mergeable ] The override flagged mergeable.
28
+ #
29
+ # @since 1.0.0
30
+ def override
31
+ use(:__override__)
32
+ end
33
+
34
+ # Instruct the next mergeable call to use union.
35
+ #
36
+ # @example Use union on the next call.
37
+ # mergeable.union.in(field: [ 1, 2, 3 ])
38
+ #
39
+ # @return [ Mergeable ] The union flagged mergeable.
40
+ #
41
+ # @since 1.0.0
42
+ def union
43
+ use(:__union__)
44
+ end
45
+
46
+ private
47
+
48
+ # Adds the criterion to the existing selection.
49
+ #
50
+ # @api private
51
+ #
52
+ # @example Add the criterion.
53
+ # mergeable.__add__({ name: 1 }, "$in")
54
+ #
55
+ # @param [ Hash ] criterion The criteria.
56
+ # @param [ String ] operator The MongoDB operator.
57
+ #
58
+ # @return [ Mergeable ] The new mergeable.
59
+ #
60
+ # @since 1.0.0
61
+ def __add__(criterion, operator)
62
+ with_strategy(:__add__, criterion, operator)
63
+ end
64
+
65
+ # Adds the criterion to the existing selection.
66
+ #
67
+ # @api private
68
+ #
69
+ # @example Add the criterion.
70
+ # mergeable.__expanded__([ 1, 10 ], "$within", "$center")
71
+ #
72
+ # @param [ Hash ] criterion The criteria.
73
+ # @param [ String ] outer The outer MongoDB operator.
74
+ # @param [ String ] inner The inner MongoDB operator.
75
+ #
76
+ # @return [ Mergeable ] The new mergeable.
77
+ #
78
+ # @since 1.0.0
79
+ def __expanded__(criterion, outer, inner)
80
+ selection(criterion) do |selector, field, value|
81
+ selector.store(field, { outer => { inner => value }})
82
+ end
83
+ end
84
+
85
+ # Adds the criterion to the existing selection.
86
+ #
87
+ # @api private
88
+ #
89
+ # @example Add the criterion.
90
+ # mergeable.__intersect__([ 1, 2 ], "$in")
91
+ #
92
+ # @param [ Hash ] criterion The criteria.
93
+ # @param [ String ] operator The MongoDB operator.
94
+ #
95
+ # @return [ Mergeable ] The new mergeable.
96
+ #
97
+ # @since 1.0.0
98
+ def __intersect__(criterion, operator)
99
+ with_strategy(:__intersect__, criterion, operator)
100
+ end
101
+
102
+ # Adds the criterion to the existing selection.
103
+ #
104
+ # @api private
105
+ #
106
+ # @example Add the criterion.
107
+ # mergeable.__multi__([ 1, 2 ], "$in")
108
+ #
109
+ # @param [ Hash ] criterion The criteria.
110
+ # @param [ String ] operator The MongoDB operator.
111
+ #
112
+ # @return [ Mergeable ] The new mergeable.
113
+ #
114
+ # @since 1.0.0
115
+ def __multi__(criterion, operator)
116
+ clone.tap do |query|
117
+ sel = query.selector
118
+ criterion.flatten.each do |expr|
119
+ next unless expr
120
+ criteria = sel[operator] || []
121
+ normalized = expr.inject({}) do |hash, (field, value)|
122
+ hash.merge!(field.specify(value))
123
+ hash
124
+ end
125
+ sel.store(operator, criteria.push(normalized))
126
+ end
127
+ end
128
+ end
129
+
130
+ # Adds the criterion to the existing selection.
131
+ #
132
+ # @api private
133
+ #
134
+ # @example Add the criterion.
135
+ # mergeable.__override__([ 1, 2 ], "$in")
136
+ #
137
+ # @param [ Hash ] criterion The criteria.
138
+ # @param [ String ] operator The MongoDB operator.
139
+ #
140
+ # @return [ Mergeable ] The new mergeable.
141
+ #
142
+ # @since 1.0.0
143
+ def __override__(criterion, operator)
144
+ selection(criterion) do |selector, field, value|
145
+ selector.store(field, { operator => prepare(field, operator, value) })
146
+ end
147
+ end
148
+
149
+ # Adds the criterion to the existing selection.
150
+ #
151
+ # @api private
152
+ #
153
+ # @example Add the criterion.
154
+ # mergeable.__union__([ 1, 2 ], "$in")
155
+ #
156
+ # @param [ Hash ] criterion The criteria.
157
+ # @param [ String ] operator The MongoDB operator.
158
+ #
159
+ # @return [ Mergeable ] The new mergeable.
160
+ #
161
+ # @since 1.0.0
162
+ def __union__(criterion, operator)
163
+ with_strategy(:__union__, criterion, operator)
164
+ end
165
+
166
+ # Prepare the value for merging.
167
+ #
168
+ # @api private
169
+ #
170
+ # @example Prepare the value.
171
+ # mergeable.prepare("field", 10)
172
+ #
173
+ # @param [ String ] field The name of the field.
174
+ # @param [ Object ] value The value.
175
+ #
176
+ # @return [ Object ] The serialized value.
177
+ #
178
+ # @since 1.0.0
179
+ def prepare(field, operator, value)
180
+ return value if operator =~ /exists|type|size/
181
+ serializer = serializers[field]
182
+ serializer ? serializer.evolve(value) : value
183
+ end
184
+
185
+ # Use the named strategy for the next operation.
186
+ #
187
+ # @api private
188
+ #
189
+ # @example Use intersection.
190
+ # mergeable.use(:__intersect__)
191
+ #
192
+ # @param [ Symbol ] strategy The strategy to use.
193
+ #
194
+ # @return [ Mergeable ] The existing mergeable.
195
+ #
196
+ # @since 1.0.0
197
+ def use(strategy)
198
+ tap do |mergeable|
199
+ mergeable.strategy = strategy
200
+ end
201
+ end
202
+
203
+ # Add criterion to the selection with the named strategy.
204
+ #
205
+ # @api private
206
+ #
207
+ # @example Add criterion with a strategy.
208
+ # selectable.with_strategy(:__union__, [ 1, 2, 3 ], "$in")
209
+ #
210
+ # @param [ Symbol ] strategy The name of the strategy method.
211
+ # @param [ Object ] criterion The criterion to add.
212
+ # @param [ String ] operator The MongoDB operator.
213
+ #
214
+ # @return [ Mergeable ] The cloned query.
215
+ #
216
+ # @since 1.0.0
217
+ def with_strategy(strategy, criterion, operator)
218
+ selection(criterion) do |selector, field, value|
219
+ selector.store(
220
+ field,
221
+ selector[field].send(strategy, { operator => prepare(field, operator, value) })
222
+ )
223
+ end
224
+ end
225
+ end
226
+ end
@@ -1,35 +1,320 @@
1
1
  # encoding: utf-8
2
2
  module Origin
3
+
4
+ # The optional module includes all behaviour that has to do with extra
5
+ # options surrounding queries, like skip, limit, sorting, etc.
3
6
  module Optional
7
+ extend Macroable
8
+
9
+ # @attribute [rw] options The query options.
10
+ attr_accessor :options
11
+
12
+ # Add ascending sorting options for all the provided fields.
13
+ #
14
+ # @example Add ascending sorting.
15
+ # optional.ascending(:first_name, :last_name)
16
+ #
17
+ # @param [ Array<Symbol> ] fields The fields to sort.
18
+ #
19
+ # @return [ Optional ] The cloned optional.
20
+ #
21
+ # @since 1.0.0
22
+ def ascending(*fields)
23
+ sort_with_list(*fields, 1)
24
+ end
25
+ alias :asc :ascending
26
+ key :asc, :override, 1
27
+ key :ascending, :override, 1
28
+
29
+ # Adds the option for telling MongoDB how many documents to retrieve in
30
+ # it's batching.
31
+ #
32
+ # @example Apply the batch size options.
33
+ # optional.batch_size(500)
34
+ #
35
+ # @param [ Integer ] value The batch size.
36
+ #
37
+ # @return [ Optional ] The cloned optional.
38
+ #
39
+ # @since 1.0.0
40
+ def batch_size(value = nil)
41
+ option(value) { |options| options.store(:batch_size, value) }
42
+ end
43
+
44
+ # Add descending sorting options for all the provided fields.
45
+ #
46
+ # @example Add descending sorting.
47
+ # optional.descending(:first_name, :last_name)
48
+ #
49
+ # @param [ Array<Symbol> ] fields The fields to sort.
50
+ #
51
+ # @return [ Optional ] The cloned optional.
52
+ #
53
+ # @since 1.0.0
54
+ def descending(*fields)
55
+ sort_with_list(*fields, -1)
56
+ end
57
+ alias :desc :descending
58
+ key :desc, :override, -1
59
+ key :descending, :override, -1
60
+
61
+ # Add an index hint to the query options.
62
+ #
63
+ # @example Add an index hint.
64
+ # optional.hint("$natural" => 1)
65
+ #
66
+ # @param [ Hash ] value The index hint.
67
+ #
68
+ # @return [ Optional ] The cloned optional.
69
+ #
70
+ # @since 1.0.0
71
+ def hint(value = nil)
72
+ option(value) { |options| options.store(:hint, value) }
73
+ end
74
+
75
+ # Add the number of documents to limit in the returned results.
76
+ #
77
+ # @example Limit the number of returned documents.
78
+ # optional.limit(20)
79
+ #
80
+ # @param [ Integer ] value The number of documents to return.
81
+ #
82
+ # @return [ Optional ] The cloned optional.
83
+ #
84
+ # @since 1.0.0
85
+ def limit(value = nil)
86
+ option(value) { |options| options.store(:limit, value.to_i) }
87
+ end
88
+
89
+ # Adds the option to limit the number of documents scanned in the
90
+ # collection.
91
+ #
92
+ # @example Add the max scan limit.
93
+ # optional.max_scan(1000)
94
+ #
95
+ # @param [ Integer ] value The max number of documents to scan.
96
+ #
97
+ # @return [ Optional ] The cloned optional.
98
+ #
99
+ # @since 1.0.0
100
+ def max_scan(value = nil)
101
+ option(value) { |options| options.store(:max_scan, value) }
102
+ end
103
+
104
+ # Tell the query not to timeout.
105
+ #
106
+ # @example Tell the query not to timeout.
107
+ # optional.no_timeout
108
+ #
109
+ # @return [ Optional ] The cloned optional.
110
+ #
111
+ # @since 1.0.0
112
+ def no_timeout
113
+ clone.tap { |query| query.options.store(:timeout, false) }
114
+ end
115
+
116
+ # Limits the results to only contain the fields provided.
117
+ #
118
+ # @example Limit the results to the provided fields.
119
+ # optional.only(:name, :dob)
120
+ #
121
+ # @param [ Array<Symbol> ] args The fields to return.
122
+ #
123
+ # @return [ Optional ] The cloned optional.
124
+ #
125
+ # @since 1.0.0
126
+ def only(*args)
127
+ option(*args) do |options|
128
+ options.store(
129
+ :fields, args.inject({}){ |sub, field| sub.tap { sub[field] = 1 }}
130
+ )
131
+ end
132
+ end
133
+
134
+ # Adds sorting criterion to the options.
135
+ #
136
+ # @example Add sorting options via a hash with integer directions.
137
+ # optional.order_by(name: 1, dob: -1)
138
+ #
139
+ # @example Add sorting options via a hash with symbol directions.
140
+ # optional.order_by(name: :asc, dob: :desc)
141
+ #
142
+ # @example Add sorting options via a hash with string directions.
143
+ # optional.order_by(name: "asc", dob: "desc")
144
+ #
145
+ # @example Add sorting options via an array with integer directions.
146
+ # optional.order_by([[ name, 1 ], [ dob, -1 ]])
147
+ #
148
+ # @example Add sorting options via an array with symbol directions.
149
+ # optional.order_by([[ name, :asc ], [ dob, :desc ]])
150
+ #
151
+ # @example Add sorting options via an array with string directions.
152
+ # optional.order_by([[ name, "asc" ], [ dob, "desc" ]])
153
+ #
154
+ # @example Add sorting options with keys.
155
+ # optional.order_by(:name.asc, :dob.desc)
156
+ #
157
+ # @example Add sorting options via a string.
158
+ # optional.order_by("name ASC, dob DESC")
159
+ #
160
+ # @param [ Array, Hash, String ] spec The sorting specification.
161
+ #
162
+ # @return [ Optional ] The cloned optional.
163
+ #
164
+ # @since 1.0.0
165
+ def order_by(*spec)
166
+ option(spec) do |options|
167
+ spec.compact.each do |criterion|
168
+ criterion.__sort_option__.each_pair do |field, direction|
169
+ add_sort_option(options, field, direction)
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ # Add the number of documents to skip.
176
+ #
177
+ # @example Add the number to skip.
178
+ # optional.skip(100)
179
+ #
180
+ # @param [ Integer ] value The number to skip.
181
+ #
182
+ # @return [ Optional ] The cloned optional.
183
+ #
184
+ # @since 1.0.0
185
+ def skip(value = nil)
186
+ option(value) { |options| options.store(:skip, value.to_i) }
187
+ end
188
+
189
+ # Limit the returned results via slicing embedded arrays.
190
+ #
191
+ # @example Slice the returned results.
192
+ # optional.slice(aliases: [ 0, 5 ])
193
+ #
194
+ # @param [ Hash ] criterion The slice options.
195
+ #
196
+ # @return [ Optional ] The cloned optional.
197
+ #
198
+ # @since 1.0.0
199
+ def slice(criterion = nil)
200
+ option(criterion) do |options|
201
+ options.__union__(
202
+ fields: criterion.inject({}) do |option, (field, val)|
203
+ option.tap { |opt| opt.store(field, { "$slice" => val }) }
204
+ end
205
+ )
206
+ end
207
+ end
208
+
209
+ # Tell the query to operate in snapshot mode.
210
+ #
211
+ # @example Add the snapshot option.
212
+ # optional.snapshot
213
+ #
214
+ # @return [ Optional ] The cloned optional.
215
+ #
216
+ # @since 1.0.0
217
+ def snapshot
218
+ clone.tap do |query|
219
+ query.options.store(:snapshot, true)
220
+ end
221
+ end
222
+
223
+ # Limits the results to only contain the fields not provided.
224
+ #
225
+ # @example Limit the results to the fields not provided.
226
+ # optional.without(:name, :dob)
227
+ #
228
+ # @param [ Array<Symbol> ] args The fields to ignore.
229
+ #
230
+ # @return [ Optional ] The cloned optional.
231
+ #
232
+ # @since 1.0.0
233
+ def without(*args)
234
+ option(*args) do |options|
235
+ options.store(
236
+ :fields, args.inject({}){ |sub, field| sub.tap { sub[field] = 0 }}
237
+ )
238
+ end
239
+ end
240
+
241
+ private
242
+
243
+ # Add a single sort option.
244
+ #
245
+ # @api private
246
+ #
247
+ # @example Add a single sort option.
248
+ # optional.add_sort_option({}, :name, 1)
249
+ #
250
+ # @param [ Hash ] options The options.
251
+ # @param [ String ] field The field name.
252
+ # @param [ Integer ] direction The sort direction.
253
+ #
254
+ # @return [ Optional ] The cloned optional.
255
+ #
256
+ # @since 1.0.0
257
+ def add_sort_option(options, field, direction)
258
+ sorting = (options[:sort] || {}).dup
259
+ sorting[field] = direction
260
+ options.store(:sort, sorting)
261
+ end
262
+
263
+ # Take the provided criterion and store it as an option in the query
264
+ # options.
265
+ #
266
+ # @api private
267
+ #
268
+ # @example Store the option.
269
+ # optional.option({ skip: 10 })
270
+ #
271
+ # @param [ Array ] args The options.
272
+ #
273
+ # @return [ Queryable ] The cloned queryable.
274
+ #
275
+ # @since 1.0.0
276
+ def option(*args)
277
+ clone.tap do |query|
278
+ unless args.compact.empty?
279
+ yield(query.options)
280
+ end
281
+ end
282
+ end
283
+
284
+ # Add multiple sort options at once.
285
+ #
286
+ # @api private
287
+ #
288
+ # @example Add multiple sort options.
289
+ # optional.sort_with_list(:name, :dob, 1)
290
+ #
291
+ # @param [ Array<String> ] fields The field names.
292
+ # @param [ Integer ] direction The sort direction.
293
+ #
294
+ # @return [ Optional ] The cloned optional.
295
+ #
296
+ # @since 1.0.0
297
+ def sort_with_list(*fields, direction)
298
+ option(fields) do |options|
299
+ fields.flatten.compact.each do |field|
300
+ add_sort_option(options, field, direction)
301
+ end
302
+ end
303
+ end
304
+
305
+ class << self
4
306
 
5
- autoload :BatchSize, "origin/optional/batch_size"
6
- autoload :Hint, "origin/optional/hint"
7
- autoload :Limit, "origin/optional/limit"
8
- autoload :MaxScan, "origin/optional/max_scan"
9
- autoload :NoTimeout, "origin/optional/no_timeout"
10
- autoload :Only, "origin/optional/only"
11
- autoload :Read, "origin/optional/read"
12
- autoload :ReturnKey, "origin/optional/return_key"
13
- autoload :ShowDiskLoc, "origin/optional/show_disk_loc"
14
- autoload :Skip, "origin/optional/skip"
15
- autoload :Slice, "origin/optional/slice"
16
- autoload :Snapshot, "origin/optional/snapshot"
17
- autoload :Transformer, "origin/optional/transformer"
18
- autoload :Without, "origin/optional/without"
19
-
20
- include BatchSize
21
- include Hint
22
- include Limit
23
- include MaxScan
24
- include NoTimeout
25
- include Only
26
- include Read
27
- include ReturnKey
28
- include ShowDiskLoc
29
- include Skip
30
- include Slice
31
- include Snapshot
32
- include Transformer
33
- include Without
307
+ # Get the methods on the optional that can be forwarded to from a model.
308
+ #
309
+ # @example Get the forwardable methods.
310
+ # Optional.forwardables
311
+ #
312
+ # @return [ Array<Symbol> ] The names of the forwardable methods.
313
+ #
314
+ # @since 1.0.0
315
+ def forwardables
316
+ public_instance_methods(false) - [ :options, :options= ]
317
+ end
318
+ end
34
319
  end
35
320
  end