stretchy-model 0.1.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.
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