stretchy-model 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +1 -0
  3. data/.yardopts +1 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +84 -0
  6. data/Gemfile +10 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +146 -0
  9. data/Rakefile +4 -0
  10. data/containers/Dockerfile.elasticsearch +7 -0
  11. data/containers/Dockerfile.opensearch +19 -0
  12. data/docker-compose.yml +52 -0
  13. data/lib/active_model/type/array.rb +13 -0
  14. data/lib/active_model/type/hash.rb +15 -0
  15. data/lib/rails/instrumentation/publishers.rb +29 -0
  16. data/lib/rails/instrumentation/railtie.rb +29 -0
  17. data/lib/stretchy/associations/associated_validator.rb +17 -0
  18. data/lib/stretchy/associations/elastic_relation.rb +38 -0
  19. data/lib/stretchy/associations.rb +161 -0
  20. data/lib/stretchy/common.rb +33 -0
  21. data/lib/stretchy/delegation/delegate_cache.rb +131 -0
  22. data/lib/stretchy/delegation/gateway_delegation.rb +43 -0
  23. data/lib/stretchy/indexing/bulk.rb +48 -0
  24. data/lib/stretchy/model/callbacks.rb +31 -0
  25. data/lib/stretchy/model/serialization.rb +20 -0
  26. data/lib/stretchy/null_relation.rb +53 -0
  27. data/lib/stretchy/persistence.rb +43 -0
  28. data/lib/stretchy/querying.rb +20 -0
  29. data/lib/stretchy/record.rb +57 -0
  30. data/lib/stretchy/refreshable.rb +15 -0
  31. data/lib/stretchy/relation.rb +169 -0
  32. data/lib/stretchy/relations/finder_methods.rb +39 -0
  33. data/lib/stretchy/relations/merger.rb +179 -0
  34. data/lib/stretchy/relations/query_builder.rb +265 -0
  35. data/lib/stretchy/relations/query_methods.rb +578 -0
  36. data/lib/stretchy/relations/search_option_methods.rb +34 -0
  37. data/lib/stretchy/relations/spawn_methods.rb +60 -0
  38. data/lib/stretchy/repository.rb +10 -0
  39. data/lib/stretchy/scoping/default.rb +134 -0
  40. data/lib/stretchy/scoping/named.rb +68 -0
  41. data/lib/stretchy/scoping/scope_registry.rb +34 -0
  42. data/lib/stretchy/scoping.rb +28 -0
  43. data/lib/stretchy/shared_scopes.rb +34 -0
  44. data/lib/stretchy/utils.rb +69 -0
  45. data/lib/stretchy/version.rb +5 -0
  46. data/lib/stretchy.rb +38 -0
  47. data/sig/stretchy.rbs +4 -0
  48. data/stretchy.logo.png +0 -0
  49. metadata +247 -0
@@ -0,0 +1,578 @@
1
+ module Stretchy
2
+ module Relations
3
+ module QueryMethods
4
+ extend ActiveSupport::Concern
5
+
6
+
7
+ MULTI_VALUE_METHODS = [
8
+ :where,
9
+ :order,
10
+ :field,
11
+ :highlight,
12
+ :source,
13
+ :must_not,
14
+ :should,
15
+ :query_string,
16
+ :aggregation,
17
+ :search_option,
18
+ :filter,
19
+ :or_filter,
20
+ :extending,
21
+ :skip_callbacks
22
+ ]
23
+
24
+ SINGLE_VALUE_METHODS = [:size]
25
+
26
+ class WhereChain
27
+ def initialize(scope)
28
+ @scope = scope
29
+ end
30
+ end
31
+
32
+
33
+ MULTI_VALUE_METHODS.each do |name|
34
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
35
+ def #{name}_values # def select_values
36
+ @values[:#{name}] || [] # @values[:select] || []
37
+ end # end
38
+ #
39
+ def #{name}_values=(values) # def select_values=(values)
40
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
41
+ @values[:#{name}] = values # @values[:select] = values
42
+ end # end
43
+ CODE
44
+ end
45
+
46
+ SINGLE_VALUE_METHODS.each do |name|
47
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
48
+ def #{name}_value # def readonly_value
49
+ @values[:#{name}] # @values[:readonly]
50
+ end # end
51
+ CODE
52
+ end
53
+
54
+ SINGLE_VALUE_METHODS.each do |name|
55
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
56
+ def #{name}_value=(value) # def readonly_value=(value)
57
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
58
+ @values[:#{name}] = value # @values[:readonly] = value
59
+ end # end
60
+ CODE
61
+ end
62
+
63
+
64
+ # Allows you to add one or more sorts on specified fields.
65
+ #
66
+ # @overload order(attribute: direction, ...)
67
+ # @param attribute [Symbol] the attribute to sort by
68
+ # @param direction [Symbol] the direction to sort in (:asc or :desc)
69
+ #
70
+ # @overload order(attribute: {order: direction, mode: mode, ...}, ...)
71
+ # @param params [Hash] attributes to sort by
72
+ # @param params [Symbol] :attribute the attribute name as key to sort by
73
+ # @param options [Hash] a hash containing possible sorting options
74
+ # @option options [Symbol] :order the direction to sort in (:asc or :desc)
75
+ # @option options [Symbol] :mode the mode to use for sorting (:avg, :min, :max, :sum, :median)
76
+ # @option options [Symbol] :numeric_type the numeric type to use for sorting (:double, :long, :date, :date_nanos)
77
+ # @option options [Symbol] :missing the value to use for documents without the field
78
+ # @option options [Hash] :nested the nested sorting options
79
+ # @option nested [String] :path the path to the nested object
80
+ # @option nested [Hash] :filter the filter to apply to the nested object
81
+ # @option nested [Hash] :max_children the maximum number of children to consider per root document when picking the sort value. Defaults to unlimited
82
+ #
83
+ # @example
84
+ # Model.order(created_at: :asc)
85
+ # # Elasticsearch equivalent
86
+ # #=> "sort" : [{"created_at" : "asc"}]
87
+ #
88
+ # Model.order(age: :desc, name: :asc, price: {order: :desc, mode: :avg})
89
+ #
90
+ # # Elasticsearch equivalent
91
+ # #=> "sort" : [
92
+ # { "price" : {"order" : "desc", "mode": "avg"}},
93
+ # { "name" : "asc" },
94
+ # { "age" : "desc" }
95
+ # ]
96
+ #
97
+ # @return [Stretchy::Relation] a new relation with the specified order
98
+ # @see #sort
99
+ def order(*args)
100
+ check_if_method_has_arguments!(:order, args)
101
+ spawn.order!(*args)
102
+ end
103
+
104
+ def order!(*args) # :nodoc:
105
+ self.order_values += args.first.zip.map(&:to_h)
106
+ self
107
+ end
108
+
109
+ # Alias for {#order}
110
+ # @see #order
111
+ alias :sort :order
112
+
113
+
114
+ # Allows you to skip callbacks for the specified fields that are added by query_must_have for
115
+ # the current query.
116
+ #
117
+ # @example
118
+ # Model.skip_callbacks(:routing)
119
+ def skip_callbacks(*args)
120
+ spawn.skip_callbacks!(*args)
121
+ end
122
+
123
+ def skip_callbacks!(*args) # :nodoc:
124
+ self.skip_callbacks_values += args
125
+ self
126
+ end
127
+
128
+ alias :sort :order
129
+
130
+
131
+ # Sets the maximum number of records to be retrieved.
132
+ #
133
+ # @param args [Integer] the maximum number of records to retrieve
134
+ #
135
+ # @example
136
+ # Model.size(10)
137
+ #
138
+ # @return [ActiveRecord::Relation] a new relation, which reflects the limit
139
+ # @see #limit
140
+ def size(args)
141
+ spawn.size!(args)
142
+ end
143
+
144
+ def size!(args) # :nodoc:
145
+ self.size_value = args
146
+ self
147
+ end
148
+
149
+ # Alias for {#size}
150
+ # @see #size
151
+ alias :limit :size
152
+
153
+
154
+
155
+
156
+ # Adds conditions to the query.
157
+ #
158
+ # Each argument is a hash where the key is the attribute to filter by and the value is the value to match.
159
+ #
160
+ # @overload where(*rest)
161
+ # @param rest [Array<Hash>] keywords containing attribute-value pairs to match
162
+ #
163
+ # @example
164
+ # Model.where(price: 10, color: :green)
165
+ #
166
+ # # Elasticsearch equivalent
167
+ # # => "query" : {
168
+ # "bool" : {
169
+ # "must" : [
170
+ # { "term" : { "price" : 10 } },
171
+ # { "term" : { "color" : "green" } }
172
+ # ]
173
+ # }
174
+ # }
175
+ #
176
+ # @return [ActiveRecord::Relation, WhereChain] a new relation, which reflects the conditions, or a WhereChain if opts is :chain
177
+ # @see #must
178
+ def where(opts = :chain, *rest)
179
+ if opts == :chain
180
+ WhereChain.new(spawn)
181
+ elsif opts.blank?
182
+ self
183
+ else
184
+ spawn.where!(opts, *rest)
185
+ end
186
+ end
187
+
188
+
189
+ def where!(opts, *rest) # :nodoc:
190
+ if opts == :chain
191
+ WhereChain.new(self)
192
+ else
193
+ self.where_values += build_where(opts, rest)
194
+ self
195
+ end
196
+ end
197
+
198
+ # Alias for {#where}
199
+ # @see #where
200
+ alias :must :where
201
+
202
+
203
+
204
+
205
+ # Adds a query string to the search.
206
+ #
207
+ # The query string uses Elasticsearch's Query String Query syntax, which includes a series of terms and operators.
208
+ # Terms can be single words or phrases. Operators include AND, OR, and NOT, among others.
209
+ # Field names can be included in the query string to search for specific values in specific fields. (e.g. "eye_color: green")
210
+ # The default operator between terms are treated as OR operators.
211
+ #
212
+ # @param query [String] the query string
213
+ # @param rest [Array] additional arguments (not normally used)
214
+ #
215
+ # @example
216
+ # Model.query_string("((big cat) OR (domestic cat)) AND NOT panther eye_color: green")
217
+ #
218
+ # @return [Stretchy::Relation] a new relation, which reflects the query string
219
+ def query_string(opts = :chain, *rest)
220
+ if opts == :chain
221
+ WhereChain.new(spawn)
222
+ elsif opts.blank?
223
+ self
224
+ else
225
+ spawn.query_string!(opts, *rest)
226
+ end
227
+ end
228
+
229
+ def query_string!(opts, *rest) # :nodoc:
230
+ if opts == :chain
231
+ WhereChain.new(self)
232
+ else
233
+ self.query_string_values += build_where(opts, rest)
234
+ self
235
+ end
236
+ end
237
+
238
+
239
+
240
+ # Adds negated conditions to the query.
241
+ #
242
+ # Each argument is a hash where the key is the attribute to filter by and the value is the value to exclude.
243
+ #
244
+ # @overload must_not(*rest)
245
+ # @param rest [Array<Hash>] a hash containing attribute-value pairs to exclude
246
+ #
247
+ # @example
248
+ # Model.must_not(color: 'blue', size: :large)
249
+ #
250
+ # @return [Stretchy::Relation] a new relation, which reflects the negated conditions
251
+ # @see #where_not
252
+ def must_not(opts = :chain, *rest)
253
+ if opts == :chain
254
+ WhereChain.new(spawn)
255
+ elsif opts.blank?
256
+ self
257
+ else
258
+ spawn.must_not!(opts, *rest)
259
+ end
260
+ end
261
+
262
+
263
+ def must_not!(opts, *rest) # :nodoc:
264
+ if opts == :chain
265
+ WhereChain.new(self)
266
+ else
267
+ self.must_not_values += build_where(opts, rest)
268
+ self
269
+ end
270
+ end
271
+
272
+ # Alias for {#must_not}
273
+ # @see #must_not
274
+ alias :where_not :must_not
275
+
276
+
277
+
278
+ # Adds optional conditions to the query.
279
+ #
280
+ # Each argument is a hash where the key is the attribute to filter by and the value is the value to match optionally.
281
+ #
282
+ # @overload should(*rest)
283
+ # @param rest [Array<Hash>] additional keywords containing attribute-value pairs to match optionally
284
+ #
285
+ # @example
286
+ # Model.should(color: :pink, size: :medium)
287
+ #
288
+ # @return [Stretchy::Relation] a new relation, which reflects the optional conditions
289
+ def should(opts = :chain, *rest)
290
+ if opts == :chain
291
+ WhereChain.new(spawn)
292
+ elsif opts.blank?
293
+ self
294
+ else
295
+ spawn.should!(opts, *rest)
296
+ end
297
+ end
298
+
299
+ def should!(opts, *rest) # :nodoc:
300
+ if opts == :chain
301
+ WhereChain.new(self)
302
+ else
303
+ self.should_values += build_where(opts, rest)
304
+ self
305
+ end
306
+ end
307
+
308
+
309
+
310
+
311
+ # @deprecated in elasticsearch 7.x+ use {#filter} instead
312
+ def or_filter(name, options = {}, &block)
313
+ spawn.or_filter!(name, options, &block)
314
+ end
315
+
316
+ def or_filter!(name, options = {}, &block) # :nodoc:
317
+ self.or_filter_values += [{name: name, args: options}]
318
+ self
319
+ end
320
+
321
+ # Adds a filter to the query.
322
+ #
323
+ # This method supports all filters supported by Elasticsearch.
324
+ #
325
+ # @overload filter(type, opts)
326
+ # @param type [Symbol] the type of filter to add (:range, :term, etc.)
327
+ # @param opts [Hash] a hash containing the attribute and value to filter by
328
+ #
329
+ # @example
330
+ # Model.filter(:range, age: {gte: 30})
331
+ # Model.filter(:term, color: :blue)
332
+ #
333
+ # @return [Stretchy::Relation] a new relation, which reflects the filter
334
+ def filter(name, options = {}, &block)
335
+ spawn.filter!(name, options, &block)
336
+ end
337
+
338
+ def filter!(name, options = {}, &block) # :nodoc:
339
+ self.filter_values += [{name: name, args: options}]
340
+ self
341
+ end
342
+
343
+
344
+
345
+ # Adds an aggregation to the query.
346
+ #
347
+ # @param name [Symbol, String] the name of the aggregation
348
+ # @param options [Hash] a hash of options for the aggregation
349
+ # @param block [Proc] an optional block to further configure the aggregation
350
+ #
351
+ # @example
352
+ # Model.aggregation(:avg_price, field: :price)
353
+ # Model.aggregation(:price_ranges) do
354
+ # range field: :price, ranges: [{to: 100}, {from: 100, to: 200}, {from: 200}]
355
+ # end
356
+ #
357
+ # Aggregation results are available in the `aggregations` method of the results under name provided in the aggregation.
358
+ #
359
+ # @example
360
+ # results = Model.where(color: :blue).aggregation(:avg_price, field: :price)
361
+ # results.aggregations.avg_price
362
+ #
363
+ # @return [Stretchy::Relation] a new relation
364
+ def aggregation(name, options = {}, &block)
365
+ spawn.aggregation!(name, options, &block)
366
+ end
367
+
368
+ def aggregation!(name, options = {}, &block) # :nodoc:
369
+ self.aggregation_values += [{name: name, args: assume_keyword_field(options)}]
370
+ self
371
+ end
372
+
373
+
374
+
375
+
376
+ def field(*args)
377
+ spawn.field!(*args)
378
+ end
379
+ alias :fields :field
380
+
381
+ def field!(*args) # :nodoc:
382
+ self.field_values += args
383
+ self
384
+ end
385
+
386
+
387
+
388
+ # Controls which fields of the source are returned.
389
+ #
390
+ # This method supports source filtering, which allows you to include or exclude fields from the source.
391
+ # You can specify fields directly, use wildcard patterns, or use an object containing arrays
392
+ # of includes and excludes patterns.
393
+ #
394
+ # If the includes property is specified, only source fields that match one of its patterns are returned.
395
+ # You can exclude fields from this subset using the excludes property.
396
+ #
397
+ # If the includes property is not specified, the entire document source is returned, excluding any
398
+ # fields that match a pattern in the excludes property.
399
+ #
400
+ # @overload source(opts)
401
+ # @param opts [Hash, Boolean] a hash containing :includes and/or :excludes arrays, or a boolean indicating whether
402
+ # to include the source
403
+ #
404
+ # @example
405
+ # Model.source(includes: [:name, :email])
406
+ # Model.source(excludes: [:name, :email])
407
+ # Model.source(false) # don't include source
408
+ #
409
+ # @return [Stretchy::Relation] a new relation, which reflects the source filtering
410
+ def source(*args)
411
+ spawn.source!(*args)
412
+ end
413
+
414
+ def source!(*args) # :nodoc:
415
+ self.source_values += args
416
+ self
417
+ end
418
+
419
+
420
+
421
+ # Checks if a field exists in the documents.
422
+ #
423
+ # This is a helper for the exists filter in Elasticsearch, which returns documents
424
+ # that have at least one non-null value in the specified field.
425
+ #
426
+ # @param field [Symbol, String] the field to check for existence
427
+ #
428
+ # @example
429
+ # Model.has_field(:name)
430
+ #
431
+ # @return [ActiveRecord::Relation] a new relation, which reflects the exists filter
432
+ def has_field(field)
433
+ spawn.filter(:exists, {field: field})
434
+ end
435
+
436
+
437
+
438
+
439
+ def bind(value)
440
+ spawn.bind!(value)
441
+ end
442
+
443
+ def bind!(value) # :nodoc:
444
+ self.bind_values += [value]
445
+ self
446
+ end
447
+
448
+
449
+
450
+
451
+
452
+ # Highlights the specified fields in the search results.
453
+ #
454
+ # @example
455
+ # Model.where(body: "turkey").highlight(:body)
456
+ #
457
+ # @param [Hash] args The fields to highlight. Each field is a key in the hash,
458
+ # and the value is another hash specifying the type of highlighting.
459
+ # For example, `{body: {type: :plain}}` will highlight the 'body' field
460
+ # with plain type highlighting.
461
+ #
462
+ # @return [Stretchy::Relation] Returns a Stretchy::Relation object, which can be used
463
+ # for chaining further query methods.
464
+ def highlight(*args)
465
+ spawn.highlight!(*args)
466
+ end
467
+
468
+ def highlight!(*args) # :nodoc:
469
+ self.highlight_values += args
470
+ self
471
+ end
472
+
473
+
474
+ # Returns a chainable relation with zero records.
475
+ def none
476
+ extending(NullRelation)
477
+ end
478
+
479
+ def none! # :nodoc:
480
+ extending!(NullRelation)
481
+ end
482
+
483
+
484
+ def extending(*modules, &block)
485
+ if modules.any? || block
486
+ spawn.extending!(*modules, &block)
487
+ else
488
+ self
489
+ end
490
+ end
491
+
492
+ def extending!(*modules, &block) # :nodoc:
493
+ modules << Module.new(&block) if block
494
+ modules.flatten!
495
+
496
+ self.extending_values += modules
497
+ extend(*extending_values) if extending_values.any?
498
+
499
+ self
500
+ end
501
+
502
+ def build_where(opts, other = [])
503
+ case opts
504
+ when String, Array
505
+ #TODO: Remove duplication with: /activerecord/lib/active_record/sanitization.rb:113
506
+ values = Hash === other.first ? other.first.values : other
507
+
508
+ values.grep(Stretchy::Relation) do |rel|
509
+ self.bind_values += rel.bind_values
510
+ end
511
+
512
+ [other.empty? ? opts : ([opts] + other)]
513
+ when Hash
514
+ [other.empty? ? opts : ([opts] + other)]
515
+ else
516
+ [opts]
517
+ end
518
+ end
519
+
520
+ private
521
+
522
+ # If terms are used, we assume that the field is a keyword field
523
+ # and append .keyword to the field name
524
+ # {terms: {field: 'gender'}}
525
+ # or nested aggs
526
+ # {terms: {field: 'gender'}, aggs: {name: {terms: {field: 'position.name'}}}}
527
+ # should be converted to
528
+ # {terms: {field: 'gender.keyword'}, aggs: {name: {terms: {field: 'position.name.keyword'}}}}
529
+ # {date_histogram: {field: 'created_at', interval: 'day'}}
530
+ # TODO: There may be cases where we don't want to add .keyword to the field and there should be a way to override this
531
+ KEYWORD_AGGREGATION_FIELDS = [:terms, :rare_terms, :significant_terms, :cardinality, :string_stats]
532
+ def assume_keyword_field(args={}, parent_match=false)
533
+ if args.is_a?(Hash)
534
+ args.each do |k, v|
535
+ if v.is_a?(Hash)
536
+ assume_keyword_field(v, KEYWORD_AGGREGATION_FIELDS.include?(k))
537
+ else
538
+ next unless v.is_a?(String) || v.is_a?(Symbol)
539
+ args[k] = ([:field, :fields].include?(k.to_sym) && v !~ /\.keyword$/ && parent_match) ? "#{v}.keyword" : v.to_s
540
+ end
541
+ end
542
+ end
543
+ end
544
+
545
+ def check_if_method_has_arguments!(method_name, args)
546
+ if args.blank?
547
+ raise ArgumentError, "The method .#{method_name}() must contain arguments."
548
+ end
549
+ end
550
+
551
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
552
+ 'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
553
+
554
+ def validate_order_args(args)
555
+ args.each do |arg|
556
+ next unless arg.is_a?(Hash)
557
+ arg.each do |_key, value|
558
+ raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
559
+ "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
560
+ end
561
+ end
562
+ end
563
+
564
+ def add_relations_to_bind_values(attributes)
565
+ if attributes.is_a?(Hash)
566
+ attributes.each_value do |value|
567
+ if value.is_a?(ActiveRecord::Relation)
568
+ self.bind_values += value.bind_values
569
+ else
570
+ add_relations_to_bind_values(value)
571
+ end
572
+ end
573
+ end
574
+ end
575
+ end
576
+
577
+ end
578
+ end
@@ -0,0 +1,34 @@
1
+ module Stretchy
2
+ module Relations
3
+
4
+ module SearchOptionMethods
5
+ extend ActiveSupport::Concern
6
+
7
+ def routing(args)
8
+ check_if_method_has_arguments!(:routing, args)
9
+ spawn.routing!(args)
10
+ end
11
+
12
+ def routing!(args)
13
+ merge_search_option_values(:routing, args)
14
+ self
15
+ end
16
+
17
+ def search_options(*args)
18
+ spawn.search_options!(*args)
19
+ end
20
+
21
+ def search_options!(*args)
22
+ self.search_option_values += args
23
+ self
24
+ end
25
+
26
+ private
27
+
28
+ def merge_search_option_values(key, value)
29
+ self.search_option_values += [Hash[key,value]]
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,60 @@
1
+ require 'active_support/core_ext/hash/except'
2
+ require 'active_support/core_ext/hash/slice'
3
+
4
+ module Stretchy
5
+ module Relations
6
+
7
+ module SpawnMethods
8
+ def spawn
9
+ clone
10
+ end
11
+
12
+ def merge(other)
13
+ if other.is_a?(Array)
14
+ to_a & other
15
+ elsif other
16
+ spawn.merge!(other)
17
+ else
18
+ self
19
+ end
20
+ end
21
+
22
+ def merge!(other) # :nodoc:
23
+ if !other.is_a?(Relation) && other.respond_to?(:to_proc)
24
+ instance_exec(&other)
25
+ else
26
+ klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
27
+ klass.new(self, other).merge
28
+ end
29
+ end
30
+
31
+ # Removes from the query the condition(s) specified in +skips+.
32
+ #
33
+ # Post.order('id asc').except(:order) # discards the order condition
34
+ # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
35
+ def except(*skips)
36
+ relation_with values.except(*skips)
37
+ end
38
+
39
+ # Removes any condition from the query other than the one(s) specified in +onlies+.
40
+ #
41
+ # Post.order('id asc').only(:where) # discards the order condition
42
+ # Post.order('id asc').only(:where, :order) # uses the specified order
43
+ def only(*onlies)
44
+ if onlies.any? { |o| o == :where }
45
+ onlies << :bind
46
+ end
47
+ relation_with values.slice(*onlies)
48
+ end
49
+
50
+ private
51
+
52
+ def relation_with(values) # :nodoc:
53
+ result = Relation.create(klass, values)
54
+ result.extend(*extending_values) if extending_values.any?
55
+ result
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,10 @@
1
+ module Stretchy
2
+ class Repository
3
+
4
+ include Elasticsearch::Persistence::Repository
5
+ include Elasticsearch::Persistence::Repository::DSL
6
+
7
+ include Stretchy::Model::Serialization
8
+
9
+ end
10
+ end