stretchy-model 0.3.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.
- checksums.yaml +4 -4
- data/README.md +21 -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/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 +21 -9
- metadata +35 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7dff610329ad21128c58429c6f9d08b62138a7fad9adb0f610984bd744db1cf
|
4
|
+
data.tar.gz: 915256c1b413dd34d4777097b878f0c2c7886342b5d8dcfedc2159a405b3bdf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66f98b878a8e78d9d79b53f05edf89a8d4a61d4fd3a5b98d93ef349491c7c87f36d4cadb857b8be5e9e992308609c22d1abc45703eb39b95e78240d87c6ae081
|
7
|
+
data.tar.gz: 0eabc3b84d0aecb0f8051bf53238ec6da5d431c7ff5d0f7c3be20888a20252504e3324c7b558fe455d90c2cf0db827b1d0440e6d03d211569ed8ed7d60dade01
|
data/README.md
CHANGED
@@ -98,16 +98,6 @@ Model.bulk_in_batches(records, size: 100) do |batch|
|
|
98
98
|
end
|
99
99
|
```
|
100
100
|
|
101
|
-
|
102
|
-
## Instrumentation
|
103
|
-
```ruby
|
104
|
-
Blanket.first
|
105
|
-
```
|
106
|
-
|
107
|
-
```sh
|
108
|
-
Blanket (6.322ms) curl -X GET 'http://localhost:9200/blankets/_search?size=1' -d '{"sort":{"date":"desc"}}'
|
109
|
-
```
|
110
|
-
|
111
101
|
## Installation
|
112
102
|
|
113
103
|
Install the gem and add to the application's Gemfile by executing:
|
@@ -131,6 +121,12 @@ rails credentials:edit
|
|
131
121
|
```yaml
|
132
122
|
elasticsearch:
|
133
123
|
url: localhost:9200
|
124
|
+
|
125
|
+
# or opensearch
|
126
|
+
# opensearch:
|
127
|
+
# host: https://localhost:9200
|
128
|
+
# user: admin
|
129
|
+
# password: admin
|
134
130
|
```
|
135
131
|
|
136
132
|
#### Create an initializer
|
@@ -154,6 +150,21 @@ After checking out the repo, run `bin/setup` to install dependencies. You can al
|
|
154
150
|
> Full documentation on [Elasticsearch Query DSL and Aggregation options](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-persistence)
|
155
151
|
|
156
152
|
## Testing
|
153
|
+
<details>
|
154
|
+
<summary>Act</summary>
|
155
|
+
|
156
|
+
Run github action workflow locally
|
157
|
+
|
158
|
+
```sh
|
159
|
+
brew install act --HEAD
|
160
|
+
```
|
161
|
+
|
162
|
+
```sh
|
163
|
+
act -P ubuntu-latest=ghcr.io/catthehacker/ubuntu:runner-latest
|
164
|
+
```
|
165
|
+
|
166
|
+
</details>
|
167
|
+
|
157
168
|
<details>
|
158
169
|
<summary>Elasticsearch</summary>
|
159
170
|
|
data/Rakefile
CHANGED
@@ -2,3 +2,95 @@
|
|
2
2
|
|
3
3
|
require "bundler/gem_tasks"
|
4
4
|
task default: %i[]
|
5
|
+
|
6
|
+
require 'octokit'
|
7
|
+
require 'versionomy'
|
8
|
+
require 'rainbow'
|
9
|
+
|
10
|
+
def determine_current_version
|
11
|
+
# Load current version
|
12
|
+
load 'lib/stretchy/version.rb'
|
13
|
+
current_version = Versionomy.parse(Stretchy::VERSION)
|
14
|
+
end
|
15
|
+
|
16
|
+
def determine_new_version(version)
|
17
|
+
# Load current version
|
18
|
+
current_version = determine_current_version
|
19
|
+
|
20
|
+
# Determine new version
|
21
|
+
case version.to_sym
|
22
|
+
when :major
|
23
|
+
current_version.bump(:major)
|
24
|
+
when :minor
|
25
|
+
current_version.bump(:minor)
|
26
|
+
when :patch
|
27
|
+
current_version.bump(:tiny)
|
28
|
+
else
|
29
|
+
version =~ /\Av?\d+\.\d+\.\d+\z/ ? Versionomy.parse(version).to_s.gsub(/v/,'') : current_version
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_release_branch(new_version, base_branch)
|
34
|
+
system("git stash save 'Changes before creating release branch'")
|
35
|
+
system("git fetch origin #{base_branch}")
|
36
|
+
branch_name = "release/v#{new_version}"
|
37
|
+
system("git checkout -b #{branch_name} #{base_branch}")
|
38
|
+
branch_name
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_version_file(new_version)
|
42
|
+
# Update lib/stretchy/version.rb
|
43
|
+
File.open('lib/stretchy/version.rb', 'w') do |file|
|
44
|
+
file.puts "module Stretchy\n VERSION = '#{new_version}'\nend"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def commit_and_push_changes(new_version, branch_name)
|
49
|
+
system("git add lib/stretchy/version.rb")
|
50
|
+
system("git commit -m 'Bump version to v#{new_version}'")
|
51
|
+
system("git tag v#{new_version}")
|
52
|
+
system("git push origin #{branch_name} --tags -f")
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_pull_request(new_version, base_branch, branch_name)
|
56
|
+
# Create a pull request
|
57
|
+
client = Octokit::Client.new(access_token: ENV['GH_TOKEN'])
|
58
|
+
client.create_pull_request('theablefew/stretchy', base_branch, branch_name, "Release v#{new_version}")
|
59
|
+
end
|
60
|
+
|
61
|
+
namespace :publish do
|
62
|
+
desc "Create a release"
|
63
|
+
task :release, [:version, :base_branch] do |t, args|
|
64
|
+
args.with_defaults(version: :patch, base_branch: 'main')
|
65
|
+
version = args[:version]
|
66
|
+
base_branch = args[:base_branch]
|
67
|
+
|
68
|
+
old_version = determine_current_version
|
69
|
+
new_version = determine_new_version(version)
|
70
|
+
puts Rainbow("Bumping version from #{old_version} to #{new_version}").green
|
71
|
+
branch_name = create_release_branch(new_version, base_branch)
|
72
|
+
begin
|
73
|
+
update_version_file(new_version)
|
74
|
+
commit_and_push_changes(new_version, branch_name)
|
75
|
+
create_pull_request(new_version, base_branch, branch_name)
|
76
|
+
rescue => e
|
77
|
+
puts "Error: #{e.message}"
|
78
|
+
puts "Rolling back changes"
|
79
|
+
system("git tag -d v#{new_version}")
|
80
|
+
system("git checkout #{base_branch}")
|
81
|
+
system("git branch -D #{branch_name}")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
task :major do
|
86
|
+
Rake::Task['publish:release'].invoke('major')
|
87
|
+
end
|
88
|
+
|
89
|
+
task :minor do
|
90
|
+
Rake::Task['publish:release'].invoke('minor')
|
91
|
+
end
|
92
|
+
|
93
|
+
task :patch do
|
94
|
+
Rake::Task['publish:release'].invoke('patch')
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Attributes
|
3
|
+
module Transformers
|
4
|
+
class KeywordTransformer
|
5
|
+
|
6
|
+
KEYWORD_AGGREGATION_KEYS = [:terms, :rare_terms, :significant_terms, :cardinality, :string_stats]
|
7
|
+
|
8
|
+
attr_reader :attribute_types
|
9
|
+
|
10
|
+
def initialize(attribute_types)
|
11
|
+
@attribute_types = attribute_types
|
12
|
+
end
|
13
|
+
|
14
|
+
def cast_value_keys
|
15
|
+
values.transform_values do |value|
|
16
|
+
case value
|
17
|
+
when Array
|
18
|
+
value.map { |item| transform_keys_for_item(item) }
|
19
|
+
when Hash
|
20
|
+
transform_keys_for_item(value)
|
21
|
+
else
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def keyword?(arg)
|
28
|
+
attr = @attribute_types[arg.to_s]
|
29
|
+
return false unless attr
|
30
|
+
attr.is_a?(Stretchy::Attributes::Type::Keyword)
|
31
|
+
end
|
32
|
+
|
33
|
+
def protected?(arg)
|
34
|
+
return false if arg.nil?
|
35
|
+
Stretchy::Relations::AggregationMethods::AGGREGATION_METHODS.include?(arg.to_sym)
|
36
|
+
end
|
37
|
+
|
38
|
+
def transform(item, *ignore)
|
39
|
+
item.each_with_object({}) do |(k, v), new_item|
|
40
|
+
if ignore && ignore.include?(k)
|
41
|
+
new_item[k] = v
|
42
|
+
next
|
43
|
+
end
|
44
|
+
new_key = (!protected?(k) && keyword?(k)) ? "#{k}.keyword" : k
|
45
|
+
|
46
|
+
new_value = v
|
47
|
+
|
48
|
+
if new_value.is_a?(Hash)
|
49
|
+
new_value = transform(new_value)
|
50
|
+
elsif new_value.is_a?(Array)
|
51
|
+
new_value = new_value.map { |i| i.is_a?(Hash) ? transform(i) : i }
|
52
|
+
elsif new_value.is_a?(String) || new_value.is_a?(Symbol)
|
53
|
+
new_value = "#{new_value}.keyword" if keyword?(new_value)
|
54
|
+
end
|
55
|
+
|
56
|
+
new_item[new_key] = new_value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# If terms are used, we assume that the field is a keyword field
|
61
|
+
# and append .keyword to the field name
|
62
|
+
# {terms: {field: 'gender'}}
|
63
|
+
# or nested aggs
|
64
|
+
# {terms: {field: 'gender'}, aggs: {name: {terms: {field: 'position.name'}}}}
|
65
|
+
# should be converted to
|
66
|
+
# {terms: {field: 'gender.keyword'}, aggs: {name: {terms: {field: 'position.name.keyword'}}}}
|
67
|
+
# {date_histogram: {field: 'created_at', interval: 'day'}}
|
68
|
+
# 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
|
69
|
+
def assume_keyword_field(args={}, parent_match=false)
|
70
|
+
if args.is_a?(Hash)
|
71
|
+
args.each do |k, v|
|
72
|
+
if v.is_a?(Hash)
|
73
|
+
assume_keyword_field(v, KEYWORD_AGGREGATION_FIELDS.include?(k))
|
74
|
+
else
|
75
|
+
next unless v.is_a?(String) || v.is_a?(Symbol)
|
76
|
+
args[k] = ([:field, :fields].include?(k.to_sym) && v !~ /\.keyword$/ && parent_match) ? "#{v}.keyword" : v.to_s
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Attributes
|
3
|
+
|
4
|
+
def self.register!
|
5
|
+
ActiveModel::Type.register(:array, ActiveModel::Type::Array)
|
6
|
+
ActiveModel::Type.register(:hash, ActiveModel::Type::Hash)
|
7
|
+
ActiveModel::Type.register(:keyword, Stretchy::Attributes::Type::Keyword)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/lib/stretchy/querying.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
module Stretchy
|
2
2
|
module Querying
|
3
3
|
delegate :first, :first!, :last, :last!, :exists?, :has_field, :any?, :many?, to: :all
|
4
|
-
delegate :order, :limit, :size, :sort, :
|
5
|
-
delegate :or_filter, :
|
6
|
-
delegate
|
7
|
-
|
8
|
-
delegate :
|
4
|
+
delegate :order, :limit, :size, :sort, :rewhere, :eager_load, :includes, :create_with, :none, :unscope, to: :all
|
5
|
+
delegate :or_filter, :fields, :source, :highlight, to: :all
|
6
|
+
delegate *Stretchy::Relations::AggregationMethods::AGGREGATION_METHODS, to: :all
|
7
|
+
|
8
|
+
delegate :skip_callbacks, :routing, :search_options, to: :all
|
9
|
+
delegate :must, :must_not, :should, :where_not, :where, :filter_query, :query_string, to: :all
|
9
10
|
|
10
11
|
def fetch_results(es)
|
11
12
|
unless es.count?
|
data/lib/stretchy/relation.rb
CHANGED
@@ -4,7 +4,7 @@ module Stretchy
|
|
4
4
|
class Relation
|
5
5
|
|
6
6
|
# These methods can accept multiple values.
|
7
|
-
MULTI_VALUE_METHODS = [:order, :where, :or_filter, :
|
7
|
+
MULTI_VALUE_METHODS = [:order, :where, :or_filter, :filter_query, :bind, :extending, :unscope, :skip_callbacks]
|
8
8
|
|
9
9
|
# These methods can accept a single value.
|
10
10
|
SINGLE_VALUE_METHODS = [:limit, :offset, :routing, :size]
|
@@ -16,7 +16,7 @@ module Stretchy
|
|
16
16
|
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
|
17
17
|
|
18
18
|
# Include modules.
|
19
|
-
include Relations::FinderMethods, Relations::SpawnMethods, Relations::QueryMethods, Relations::SearchOptionMethods, Delegation
|
19
|
+
include Relations::FinderMethods, Relations::SpawnMethods, Relations::QueryMethods, Relations::AggregationMethods, Relations::SearchOptionMethods, Delegation
|
20
20
|
|
21
21
|
# Getters.
|
22
22
|
attr_reader :klass, :loaded
|
@@ -162,7 +162,7 @@ module Stretchy
|
|
162
162
|
#
|
163
163
|
# @return [QueryBuilder] The query builder for the relation.
|
164
164
|
def query_builder
|
165
|
-
Relations::QueryBuilder.new(values)
|
165
|
+
Relations::QueryBuilder.new(values, klass.attribute_types)
|
166
166
|
end
|
167
167
|
|
168
168
|
end
|