stretchy-model 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +66 -10
- data/Rakefile +92 -0
- data/lib/stretchy/attributes/transformers/keyword_transformer.rb +85 -0
- data/lib/stretchy/attributes/type/keyword.rb +11 -0
- data/lib/stretchy/attributes.rb +10 -0
- data/lib/stretchy/open_search_compatibility.rb +86 -0
- data/lib/stretchy/querying.rb +6 -5
- data/lib/stretchy/relation.rb +3 -3
- data/lib/stretchy/relations/aggregation_methods.rb +758 -0
- data/lib/stretchy/relations/finder_methods.rb +21 -3
- data/lib/stretchy/relations/merger.rb +6 -6
- data/lib/stretchy/relations/query_builder.rb +23 -9
- data/lib/stretchy/relations/query_methods.rb +10 -36
- data/lib/stretchy/utils.rb +21 -0
- data/lib/stretchy/version.rb +1 -3
- data/lib/stretchy.rb +37 -8
- metadata +50 -3
@@ -9,7 +9,15 @@ module Stretchy
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def first!
|
12
|
-
|
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
|
-
|
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
|
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, :
|
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 == :
|
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.
|
118
|
-
rhs_filters = values[:
|
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
|
-
|
125
|
+
filter_query_values = rhs_filters
|
126
126
|
|
127
127
|
|
128
128
|
relation.where_values = where_values.empty? ? nil : where_values
|
129
|
-
relation.
|
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[:
|
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[:
|
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.
|
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
|
-
|
203
|
-
|
204
|
-
|
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
|
-
:
|
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 {#
|
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
|
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.
|
331
|
-
# Model.
|
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
|
335
|
-
spawn.
|
334
|
+
def filter_query(name, options = {}, &block)
|
335
|
+
spawn.filter_query!(name, options, &block)
|
336
336
|
end
|
337
337
|
|
338
|
-
def
|
339
|
-
self.
|
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.
|
407
|
+
spawn.filter_query(:exists, {field: field})
|
434
408
|
end
|
435
409
|
|
436
410
|
|
data/lib/stretchy/utils.rb
CHANGED
@@ -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]
|
data/lib/stretchy/version.rb
CHANGED
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
|
52
|
-
|
53
|
-
|
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.
|
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-
|
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
|
292
|
+
rubygems_version: 3.5.3
|
246
293
|
signing_key:
|
247
294
|
specification_version: 4
|
248
295
|
summary: Rails ORM for Elasticsearch
|