stretchy-model 0.2.0 → 0.4.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.
@@ -9,7 +9,15 @@ module Stretchy
9
9
  end
10
10
 
11
11
  def first!
12
- spawn.sort(Hash[default_sort_key, :asc]).spawn.size(1)
12
+ spawned = spawn
13
+ if spawned.order_values.length.zero?
14
+ spawn.sort(Hash[default_sort_key, :asc]).spawn.size(1)
15
+ elsif spawned.order_values.length >= 1
16
+ first_order_value = spawned.order_values.shift
17
+ new_direction = Hash[first_order_value.keys.first, :asc]
18
+ spawned.order_values.unshift(new_direction)
19
+ spawned.size(1)
20
+ end
13
21
  self
14
22
  end
15
23
 
@@ -19,7 +27,15 @@ module Stretchy
19
27
  end
20
28
 
21
29
  def last!
22
- spawn.sort(Hash[default_sort_key, :desc]).spawn.size(1)
30
+ spawned = spawn
31
+ if spawned.order_values.length.zero?
32
+ spawn.sort(Hash[default_sort_key, :desc]).spawn.size(1)
33
+ elsif spawned.order_values.length >= 1
34
+ first_order_value = spawned.order_values.shift
35
+ new_direction = Hash[first_order_value.keys.first, :desc]
36
+ spawned.order_values.unshift(new_direction)
37
+ spawned.size(1)
38
+ end
23
39
  self
24
40
  end
25
41
 
@@ -31,7 +47,9 @@ module Stretchy
31
47
  def count!
32
48
  @values[:count] = true
33
49
  @values.delete(:size)
34
- spawn.results
50
+ spawned = spawn
51
+ spawned.order_values.clear
52
+ spawned.results
35
53
  end
36
54
 
37
55
  end
@@ -50,7 +50,7 @@ module Stretchy
50
50
  @other = other
51
51
  end
52
52
 
53
- NORMAL_VALUES = [:where, :first, :last, :filter]
53
+ NORMAL_VALUES = [:where, :first, :last, :filter_query]
54
54
 
55
55
  def normal_values
56
56
  NORMAL_VALUES
@@ -67,7 +67,7 @@ module Stretchy
67
67
  unless value.nil? || (value.blank? && false != value)
68
68
  if name == :select
69
69
  relation._select!(*value)
70
- elsif name == :filter
70
+ elsif name == :filter_query
71
71
  values.each do |v|
72
72
  relation.send("#{name}!", v.first, v.last)
73
73
  end
@@ -114,19 +114,19 @@ module Stretchy
114
114
  lhs_wheres = relation.where_values
115
115
  rhs_wheres = values[:where] || []
116
116
 
117
- lhs_filters = relation.filter_values
118
- rhs_filters = values[:filter] || []
117
+ lhs_filters = relation.filter_query_values
118
+ rhs_filters = values[:filter_query] || []
119
119
 
120
120
  removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
121
121
 
122
122
  where_values = kept + rhs_wheres
123
123
 
124
124
  filters_removed, filters_kept = partition_overwrites(lhs_wheres, rhs_wheres)
125
- filter_values = rhs_filters
125
+ filter_query_values = rhs_filters
126
126
 
127
127
 
128
128
  relation.where_values = where_values.empty? ? nil : where_values
129
- relation.filter_values = filter_values.empty? ? nil : filter_values
129
+ relation.filter_query_values = filter_query_values.empty? ? nil : filter_query_values
130
130
 
131
131
  if values[:reordering]
132
132
  # override any order specified in the original relation
@@ -2,9 +2,10 @@ module Stretchy
2
2
  module Relations
3
3
  class QueryBuilder
4
4
 
5
- attr_reader :structure, :values
5
+ attr_reader :structure, :values, :attribute_types
6
6
 
7
- def initialize(values)
7
+ def initialize(values, attribute_types = nil)
8
+ @attribute_types = attribute_types
8
9
  @structure = Jbuilder.new ignore_nil: true
9
10
  @values = values
10
11
  end
@@ -14,7 +15,7 @@ module Stretchy
14
15
  end
15
16
 
16
17
  def filters
17
- values[:filter]
18
+ values[:filter_query]
18
19
  end
19
20
 
20
21
  def or_filters
@@ -58,7 +59,7 @@ module Stretchy
58
59
  end
59
60
 
60
61
  def query_filters
61
- values[:filter]
62
+ values[:filter_query]
62
63
  end
63
64
 
64
65
  def search_options
@@ -149,7 +150,7 @@ module Stretchy
149
150
  end
150
151
 
151
152
  def build_sort
152
- structure.sort sort.flatten #.inject(Hash.new) { |h,v| h.merge(v) }
153
+ structure.sort sort.map { |arg| keyword_transformer.transform(arg) }.flatten
153
154
  end
154
155
 
155
156
  def build_highlights
@@ -165,7 +166,7 @@ module Stretchy
165
166
  def build_aggregations
166
167
  structure.aggregations do
167
168
  aggregations.each do |agg|
168
- structure.set! agg[:name], aggregation(agg[:name], agg[:args])
169
+ structure.set! agg[:name], aggregation(agg[:name], keyword_transformer.transform(agg[:args], :name))
169
170
  end
170
171
  end
171
172
  end
@@ -199,13 +200,23 @@ module Stretchy
199
200
  def as_must(q)
200
201
  _must = []
201
202
  q.each do |arg|
202
- arg.each_pair { |k,v| _must << (v.is_a?(Array) ? {terms: Hash[k,v]} : {term: Hash[k,v]}) } if arg.class == Hash
203
- _must << {term: Hash[[arg.split(/:/).collect(&:strip)]]} if arg.class == String
204
- _must << arg.first if arg.class == Array
203
+ case arg
204
+ when Hash
205
+ arg = keyword_transformer.transform(arg)
206
+ arg.each_pair do |k,v|
207
+ # If v is an array, we build a terms query otherwise a term query
208
+ _must << (v.is_a?(Array) ? {terms: Hash[k,v]} : {term: Hash[k,v]})
209
+ end
210
+ when String
211
+ _must << {term: Hash[[arg.split(/:/).collect(&:strip)]]}
212
+ when Array
213
+ _must << arg.first
214
+ end
205
215
  end
206
216
  _must.length == 1 ? _must.first : _must
207
217
  end
208
218
 
219
+
209
220
  def as_query_string(q)
210
221
  _and = []
211
222
 
@@ -260,6 +271,9 @@ module Stretchy
260
271
  end
261
272
  end
262
273
 
274
+ def keyword_transformer
275
+ @keyword_transformer ||= Stretchy::Attributes::Transformers::KeywordTransformer.new(@attribute_types)
276
+ end
263
277
  end
264
278
  end
265
279
  end
@@ -15,7 +15,7 @@ module Stretchy
15
15
  :query_string,
16
16
  :aggregation,
17
17
  :search_option,
18
- :filter,
18
+ :filter_query,
19
19
  :or_filter,
20
20
  :extending,
21
21
  :skip_callbacks
@@ -308,7 +308,7 @@ module Stretchy
308
308
 
309
309
 
310
310
 
311
- # @deprecated in elasticsearch 7.x+ use {#filter} instead
311
+ # @deprecated in elasticsearch 7.x+ use {#filter_query} instead
312
312
  def or_filter(name, options = {}, &block)
313
313
  spawn.or_filter!(name, options, &block)
314
314
  end
@@ -322,53 +322,27 @@ module Stretchy
322
322
  #
323
323
  # This method supports all filters supported by Elasticsearch.
324
324
  #
325
- # @overload filter(type, opts)
325
+ # @overload filter_query(type, opts)
326
326
  # @param type [Symbol] the type of filter to add (:range, :term, etc.)
327
327
  # @param opts [Hash] a hash containing the attribute and value to filter by
328
328
  #
329
329
  # @example
330
- # Model.filter(:range, age: {gte: 30})
331
- # Model.filter(:term, color: :blue)
330
+ # Model.filter_query(:range, age: {gte: 30})
331
+ # Model.filter_query(:term, color: :blue)
332
332
  #
333
333
  # @return [Stretchy::Relation] a new relation, which reflects the filter
334
- def filter(name, options = {}, &block)
335
- spawn.filter!(name, options, &block)
334
+ def filter_query(name, options = {}, &block)
335
+ spawn.filter_query!(name, options, &block)
336
336
  end
337
337
 
338
- def filter!(name, options = {}, &block) # :nodoc:
339
- self.filter_values += [{name: name, args: options}]
338
+ def filter_query!(name, options = {}, &block) # :nodoc:
339
+ self.filter_query_values += [{name: name, args: options}]
340
340
  self
341
341
  end
342
342
 
343
343
 
344
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
345
 
368
- def aggregation!(name, options = {}, &block) # :nodoc:
369
- self.aggregation_values += [{name: name, args: assume_keyword_field(options)}]
370
- self
371
- end
372
346
 
373
347
 
374
348
 
@@ -430,7 +404,7 @@ module Stretchy
430
404
  #
431
405
  # @return [ActiveRecord::Relation] a new relation, which reflects the exists filter
432
406
  def has_field(field)
433
- spawn.filter(:exists, {field: field})
407
+ spawn.filter_query(:exists, {field: field})
434
408
  end
435
409
 
436
410
 
@@ -2,6 +2,27 @@ module Stretchy
2
2
  module Utils
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ concerning :ConsoleMethods do
6
+ def reload!
7
+ Stretchy.loader.reload
8
+ end
9
+
10
+ def banner
11
+ banner = <<~BANNER
12
+
13
+ d8b
14
+ d8P d8P ?88
15
+ d888888P d888888P 88b
16
+ .d888b, ?88' 88bd88b d8888b ?88' d8888b 888888b ?88 d8P
17
+ ?8b, 88P 88P' `d8b_,dP 88P d8P' `P 88P `?8bd88 88
18
+ `?8b 88b d88 88b 88b 88b d88 88P?8( d88
19
+ `?888P' `?8b d88' `?888P' `?8b `?888P'd88' 88b`?88P'?8b
20
+ )88
21
+ ,d8P
22
+ `?888P'
23
+ BANNER
24
+ end
25
+ end
5
26
 
6
27
  def self.to_curl(klass, arguments = {}, end_point = "_search")
7
28
  host = klass.gateway.client.transport.transport.hosts&.first || klass.gateway.client.transport.transport.options[:url]
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Stretchy
4
- VERSION = "0.2.0"
2
+ VERSION = '0.4.0'
5
3
  end
data/lib/stretchy.rb CHANGED
@@ -10,13 +10,28 @@ require 'active_support/all'
10
10
  require 'active_model/type/array'
11
11
  require 'active_model/type/hash'
12
12
 
13
- ActiveModel::Type.register(:array, ActiveModel::Type::Array)
14
- ActiveModel::Type.register(:hash, ActiveModel::Type::Hash)
15
-
16
13
  require_relative "stretchy/version"
17
14
  require_relative "rails/instrumentation/railtie" if defined?(Rails)
18
15
 
16
+
17
+
19
18
  module Stretchy
19
+
20
+ def self.loader
21
+ @loader ||= begin
22
+ loader = Zeitwerk::Loader.new
23
+ loader.tag = File.basename(__FILE__, ".rb")
24
+ loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
25
+ loader.push_dir(__dir__)
26
+ loader
27
+ end
28
+ end
29
+
30
+ def self.boot!
31
+ loader.setup
32
+ Stretchy::Attributes.register!
33
+ end
34
+
20
35
  module Errors
21
36
  class QueryOptionMissing < StandardError; end
22
37
  end
@@ -24,9 +39,25 @@ module Stretchy
24
39
  class Configuration
25
40
 
26
41
  attr_accessor :client
42
+ attr_accessor :opensearch
27
43
 
28
44
  def initialize
29
45
  @client = Elasticsearch::Client.new url: 'http://localhost:9200'
46
+ @opensearch = false
47
+ end
48
+
49
+ def client=(client)
50
+ @client = client
51
+ self.opensearch = true if @client.class.name =~ /OpenSearch/
52
+ end
53
+
54
+ def opensearch=(bool)
55
+ @opensearch = bool
56
+ OpenSearchCompatibility.opensearch_patch! if bool
57
+ end
58
+
59
+ def opensearch?
60
+ @opensearch
30
61
  end
31
62
 
32
63
  end
@@ -48,8 +79,6 @@ module Stretchy
48
79
 
49
80
  end
50
81
 
51
- loader = Zeitwerk::Loader.new
52
- loader.tag = File.basename(__FILE__, ".rb")
53
- loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
54
- loader.push_dir(__dir__)
55
- loader.setup
82
+ Stretchy.loader.enable_reloading if defined?(Rails) && Rails.env.development? || ENV['RACK_ENV'] == 'development'
83
+ Stretchy.boot!
84
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stretchy-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Spencer Markowski
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-05 00:00:00.000000000 Z
11
+ date: 2024-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -164,6 +164,48 @@ dependencies:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
166
  version: 0.9.36
167
+ - !ruby/object:Gem::Dependency
168
+ name: opensearch-ruby
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '3.0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '3.0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: octokit
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '4.20'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '4.20'
195
+ - !ruby/object:Gem::Dependency
196
+ name: versionomy
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 0.5.0
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 0.5.0
167
209
  description: Provides a familiar ActiveRecord-like interface for working with Elasticsearch
168
210
  email:
169
211
  - spencer.markowski@theablefew.com
@@ -190,6 +232,9 @@ files:
190
232
  - lib/stretchy/associations.rb
191
233
  - lib/stretchy/associations/associated_validator.rb
192
234
  - lib/stretchy/associations/elastic_relation.rb
235
+ - lib/stretchy/attributes.rb
236
+ - lib/stretchy/attributes/transformers/keyword_transformer.rb
237
+ - lib/stretchy/attributes/type/keyword.rb
193
238
  - lib/stretchy/common.rb
194
239
  - lib/stretchy/delegation/delegate_cache.rb
195
240
  - lib/stretchy/delegation/gateway_delegation.rb
@@ -197,11 +242,13 @@ files:
197
242
  - lib/stretchy/model/callbacks.rb
198
243
  - lib/stretchy/model/serialization.rb
199
244
  - lib/stretchy/null_relation.rb
245
+ - lib/stretchy/open_search_compatibility.rb
200
246
  - lib/stretchy/persistence.rb
201
247
  - lib/stretchy/querying.rb
202
248
  - lib/stretchy/record.rb
203
249
  - lib/stretchy/refreshable.rb
204
250
  - lib/stretchy/relation.rb
251
+ - lib/stretchy/relations/aggregation_methods.rb
205
252
  - lib/stretchy/relations/finder_methods.rb
206
253
  - lib/stretchy/relations/merger.rb
207
254
  - lib/stretchy/relations/query_builder.rb
@@ -242,7 +289,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
242
289
  - !ruby/object:Gem::Version
243
290
  version: '0'
244
291
  requirements: []
245
- rubygems_version: 3.3.7
292
+ rubygems_version: 3.5.3
246
293
  signing_key:
247
294
  specification_version: 4
248
295
  summary: Rails ORM for Elasticsearch