elastic_record 5.5.0 → 5.6.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/.travis.yml +4 -4
- data/README.md +1 -1
- data/elastic_record.gemspec +1 -1
- data/lib/elastic_record/as_document.rb +21 -25
- data/lib/elastic_record/index.rb +2 -1
- data/lib/elastic_record/index/deferred.rb +1 -1
- data/lib/elastic_record/index/documents.rb +0 -92
- data/lib/elastic_record/index/manage.rb +5 -4
- data/lib/elastic_record/index/mapping.rb +2 -2
- data/lib/elastic_record/index/mapping_type.rb +10 -2
- data/lib/elastic_record/index/search.rb +103 -0
- data/lib/elastic_record/model.rb +15 -13
- data/lib/elastic_record/relation.rb +22 -7
- data/lib/elastic_record/relation/batches.rb +2 -2
- data/lib/elastic_record/relation/hits.rb +4 -1
- data/lib/elastic_record/relation/search_methods.rb +1 -1
- data/lib/elastic_record/search_hits.rb +5 -13
- data/test/dummy/app/models/widget.rb +0 -12
- data/test/elastic_record/as_document_test.rb +5 -0
- data/test/elastic_record/from_search_hits_test.rb +3 -3
- data/test/elastic_record/index/documents_test.rb +0 -51
- data/test/elastic_record/index/mapping_test.rb +15 -17
- data/test/elastic_record/index/search_test.rb +60 -0
- data/test/elastic_record/percolator_model_test.rb +14 -16
- data/test/elastic_record/relation/batches_test.rb +0 -8
- data/test/elastic_record/relation_test.rb +13 -0
- data/test/elastic_record/searching_test.rb +8 -8
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c916b053e2c2a228b4da081dad0b380dfcd7aea05c3f970134d25f68b9c9a72
|
4
|
+
data.tar.gz: 7fa353180b59dcc3fafa81a1e4d6def1d1873b1748079c1d676d38458ea7ba78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aadbe8c69f0a39e70d0cafc2bc77785cfabb1ffec23b5480eb6269cfd7cac955ff6761a2c3d375f68f0d1b625c4e09de89d7913ac84a347161fac3895d4242f1
|
7
|
+
data.tar.gz: 642e51f60c39f3102a4c716b60e3c0ec84c89b5d75ed5828c06f73934c61eebb9a0e19892a0ffa12bdc966b7c0b46608f2337ec522b333929d3ba574d0b49678
|
data/.travis.yml
CHANGED
@@ -3,19 +3,19 @@ cache: bundler
|
|
3
3
|
dist: xenial
|
4
4
|
|
5
5
|
before_install:
|
6
|
-
- wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ES_VERSION}.tar.gz
|
7
|
-
- tar -xzf elasticsearch-${ES_VERSION}.tar.gz
|
6
|
+
- wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ES_VERSION}-linux-x86_64.tar.gz
|
7
|
+
- tar -xzf elasticsearch-${ES_VERSION}-linux-x86_64.tar.gz
|
8
8
|
- ./elasticsearch-${ES_VERSION}/bin/elasticsearch -d
|
9
9
|
|
10
10
|
before_script:
|
11
11
|
- cp test/dummy/.env.example test/dummy/.env
|
12
|
-
- wget --quiet --waitretry=1 --retry-connrefused --timeout=
|
12
|
+
- for i in 1 2 3 ; do wget --quiet --waitretry=1 --retry-connrefused --timeout=30 -O - http://127.0.0.1:9200 && break ; done
|
13
13
|
- bundle exec rake app:db:setup
|
14
14
|
- bundle exec rake app:index:reset
|
15
15
|
|
16
16
|
env:
|
17
17
|
global:
|
18
|
-
- ES_VERSION=
|
18
|
+
- ES_VERSION=7.1.1
|
19
19
|
|
20
20
|
services:
|
21
21
|
- postgresql
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
[](http://travis-ci.org/data-axle/elastic_record)
|
3
3
|
[](https://codeclimate.com/github/data-axle/elastic_record)
|
4
4
|
|
5
|
-
ElasticRecord is an Elasticsearch 6.x ORM.
|
5
|
+
ElasticRecord is an Elasticsearch 6.x and 7.x ORM.
|
6
6
|
|
7
7
|
## Setup ##
|
8
8
|
|
data/elastic_record.gemspec
CHANGED
@@ -10,42 +10,38 @@ module ElasticRecord
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
def as_partial_update_document
|
14
|
-
mapping_properties = elastic_index.mapping[:properties]
|
13
|
+
def as_partial_update_document(mapping_properties = elastic_index.mapping[:properties])
|
15
14
|
changed_fields = respond_to?(:saved_changes) ? saved_changes.keys : changed
|
16
15
|
|
17
16
|
changed_fields.each_with_object({}) do |field, result|
|
18
17
|
if field_mapping = mapping_properties[field]
|
19
|
-
result[field] = value_for_elastic_search
|
18
|
+
result[field] = value_for_elastic_search(field, field_mapping, mapping_properties, partial: true)
|
20
19
|
end
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
24
|
-
def value_for_elastic_search(field, mapping, mapping_properties)
|
25
|
-
value = try
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
if value.present? || value == false
|
43
|
-
value
|
23
|
+
def value_for_elastic_search(field, mapping, mapping_properties, partial: false)
|
24
|
+
return if (value = try(field)).nil?
|
25
|
+
|
26
|
+
case mapping[:type]&.to_sym
|
27
|
+
when :object
|
28
|
+
object_mapping_properties = mapping_properties.dig(field, :properties)
|
29
|
+
value_for_elastic_search_object(value, object_mapping_properties, partial: partial)
|
30
|
+
when :nested
|
31
|
+
return nil if value.empty?
|
32
|
+
|
33
|
+
object_mapping_properties = mapping_properties.dig(field, :properties)
|
34
|
+
value.map { |entry| value_for_elastic_search_object(entry, object_mapping_properties) }
|
35
|
+
when :integer_range, :float_range, :long_range, :double_range, :date_range
|
36
|
+
value_for_elastic_search_range(value)
|
37
|
+
else
|
38
|
+
value if value.present? || value == false
|
44
39
|
end
|
45
40
|
end
|
46
41
|
|
47
|
-
def value_for_elastic_search_object(object, nested_mapping)
|
48
|
-
|
42
|
+
def value_for_elastic_search_object(object, nested_mapping, partial: false)
|
43
|
+
method = partial ? :as_partial_update_document : :as_search_document
|
44
|
+
object.respond_to?(method) ? object.public_send(method, nested_mapping) : object
|
49
45
|
end
|
50
46
|
|
51
47
|
def value_for_elastic_search_range(range)
|
data/lib/elastic_record/index.rb
CHANGED
@@ -3,6 +3,7 @@ require 'elastic_record/index/deferred'
|
|
3
3
|
require 'elastic_record/index/documents'
|
4
4
|
require 'elastic_record/index/manage'
|
5
5
|
require 'elastic_record/index/mapping'
|
6
|
+
require 'elastic_record/index/search'
|
6
7
|
require 'elastic_record/index/settings'
|
7
8
|
require 'elastic_record/index/mapping_type'
|
8
9
|
|
@@ -27,7 +28,7 @@ module ElasticRecord
|
|
27
28
|
# [update_mapping]
|
28
29
|
# Update elastic search's mapping
|
29
30
|
class Index
|
30
|
-
include Documents
|
31
|
+
include Documents, Search
|
31
32
|
include Manage
|
32
33
|
include Mapping, Settings
|
33
34
|
include Analyze
|
@@ -40,7 +40,7 @@ module ElasticRecord
|
|
40
40
|
|
41
41
|
if READ_METHODS.include?(method)
|
42
42
|
flush_deferred_actions!
|
43
|
-
if method == :json_get && args.first =~ /^\/(.*)\/
|
43
|
+
if method == :json_get && args.first =~ /^\/(.*)\/_m?search/
|
44
44
|
index.real_connection.json_post("/#{$1.partition('/').first}/_refresh")
|
45
45
|
end
|
46
46
|
|
@@ -2,62 +2,6 @@ require 'active_support/core_ext/object/to_query'
|
|
2
2
|
|
3
3
|
module ElasticRecord
|
4
4
|
class Index
|
5
|
-
class ScrollEnumerator
|
6
|
-
attr_reader :keep_alive, :batch_size, :scroll_id
|
7
|
-
def initialize(elastic_index, search: nil, scroll_id: nil, keep_alive:, batch_size:)
|
8
|
-
@elastic_index = elastic_index
|
9
|
-
@search = search
|
10
|
-
@scroll_id = scroll_id
|
11
|
-
@keep_alive = keep_alive
|
12
|
-
@batch_size = batch_size
|
13
|
-
end
|
14
|
-
|
15
|
-
def each_slice(&block)
|
16
|
-
while (hits = request_more_hits.hits).any?
|
17
|
-
hits.each_slice(batch_size, &block)
|
18
|
-
end
|
19
|
-
|
20
|
-
@elastic_index.delete_scroll(scroll_id)
|
21
|
-
end
|
22
|
-
|
23
|
-
def request_more_ids
|
24
|
-
request_more_hits.to_ids
|
25
|
-
end
|
26
|
-
|
27
|
-
def request_more_hits
|
28
|
-
SearchHits.from_response(@elastic_index.model, request_next_scroll)
|
29
|
-
end
|
30
|
-
|
31
|
-
def request_next_scroll
|
32
|
-
if scroll_id
|
33
|
-
response = @elastic_index.scroll(scroll_id, keep_alive)
|
34
|
-
|
35
|
-
if response['_scroll_id'] != scroll_id
|
36
|
-
@elastic_index.delete_scroll(scroll_id)
|
37
|
-
end
|
38
|
-
else
|
39
|
-
response = initial_search_response
|
40
|
-
end
|
41
|
-
|
42
|
-
@scroll_id = response['_scroll_id']
|
43
|
-
|
44
|
-
response
|
45
|
-
end
|
46
|
-
|
47
|
-
def total_hits
|
48
|
-
initial_search_response['hits']['total']
|
49
|
-
end
|
50
|
-
|
51
|
-
def initial_search_response
|
52
|
-
@initial_search_response ||= begin
|
53
|
-
search_options = { size: batch_size, scroll: keep_alive }
|
54
|
-
elastic_query = @search.reverse_merge('sort' => '_doc')
|
55
|
-
|
56
|
-
@elastic_index.search(elastic_query, search_options)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
5
|
module Documents
|
62
6
|
def index_record(record, index_name: alias_name)
|
63
7
|
unless disabled
|
@@ -142,42 +86,6 @@ module ElasticRecord
|
|
142
86
|
end
|
143
87
|
end
|
144
88
|
|
145
|
-
def record_exists?(id)
|
146
|
-
get(id)['found']
|
147
|
-
end
|
148
|
-
|
149
|
-
def search(elastic_query, options = {})
|
150
|
-
url = "_search"
|
151
|
-
if options.any?
|
152
|
-
url += "?#{options.to_query}"
|
153
|
-
end
|
154
|
-
|
155
|
-
get url, elastic_query.update('_source' => load_from_source)
|
156
|
-
end
|
157
|
-
|
158
|
-
def explain(id, elastic_query)
|
159
|
-
get "_explain", elastic_query
|
160
|
-
end
|
161
|
-
|
162
|
-
def build_scroll_enumerator(search: nil, scroll_id: nil, batch_size: 100, keep_alive: ElasticRecord::Config.scroll_keep_alive)
|
163
|
-
ScrollEnumerator.new(self, search: search, scroll_id: scroll_id, batch_size: batch_size, keep_alive: keep_alive)
|
164
|
-
end
|
165
|
-
|
166
|
-
def scroll(scroll_id, scroll_keep_alive)
|
167
|
-
options = {scroll_id: scroll_id, scroll: scroll_keep_alive}
|
168
|
-
connection.json_get("/_search/scroll?#{options.to_query}")
|
169
|
-
rescue ElasticRecord::ConnectionError => e
|
170
|
-
case e.status_code
|
171
|
-
when '400' then raise ElasticRecord::InvalidScrollError, e.message
|
172
|
-
when '404' then raise ElasticRecord::ExpiredScrollError, e.message
|
173
|
-
else raise e
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def delete_scroll(scroll_id)
|
178
|
-
connection.json_delete('/_search/scroll', { scroll_id: scroll_id })
|
179
|
-
end
|
180
|
-
|
181
89
|
def bulk(options = {}, &block)
|
182
90
|
if current_bulk_batch
|
183
91
|
yield
|
@@ -8,12 +8,13 @@ module ElasticRecord
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def create(index_name = new_index_name, setting_overrides: {})
|
11
|
-
|
12
|
-
"mappings" => {
|
13
|
-
mapping_type => mapping
|
14
|
-
},
|
11
|
+
mapping_params = {
|
12
|
+
"mappings" => (custom_mapping_type_name? ? { mapping_type => mapping } : mapping),
|
15
13
|
"settings" => settings.merge(setting_overrides)
|
16
14
|
}
|
15
|
+
|
16
|
+
# TODO: Remove include_type_name when ES8 support is added
|
17
|
+
connection.json_put "/#{index_name}?include_type_name=#{custom_mapping_type_name?}", mapping_params
|
17
18
|
index_name
|
18
19
|
end
|
19
20
|
|
@@ -16,11 +16,11 @@ module ElasticRecord
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def update_mapping(index_name = alias_name)
|
19
|
-
connection.json_put "/#{index_name}/_mapping
|
19
|
+
connection.json_put "/#{index_name}/_mapping?include_type_name=false", mapping
|
20
20
|
end
|
21
21
|
|
22
22
|
def get_mapping(index_name = alias_name)
|
23
|
-
json = connection.json_get "/#{index_name}/_mapping
|
23
|
+
json = connection.json_get "/#{index_name}/_mapping?include_type_name=false"
|
24
24
|
|
25
25
|
unless json.empty?
|
26
26
|
json.values.first['mappings']
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ElasticRecord
|
2
4
|
class Index
|
3
5
|
# This module facilitates the removal of multiple mapping types from ElasticSearch.
|
@@ -6,10 +8,16 @@ module ElasticRecord
|
|
6
8
|
# * 6.x - Type defaults to _doc, but any type can be specified
|
7
9
|
# * 7.x - Only _doc will be supported, effectively removing the type concept.
|
8
10
|
module MappingType
|
9
|
-
|
11
|
+
attr_writer :mapping_type
|
12
|
+
|
13
|
+
DEFAULT_MAPPING_TYPE = '_doc'
|
10
14
|
|
11
15
|
def mapping_type
|
12
|
-
@mapping_type ||
|
16
|
+
@mapping_type || DEFAULT_MAPPING_TYPE
|
17
|
+
end
|
18
|
+
|
19
|
+
def custom_mapping_type_name?
|
20
|
+
mapping_type != MappingType::DEFAULT_MAPPING_TYPE
|
13
21
|
end
|
14
22
|
end
|
15
23
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'active_support/core_ext/object/to_query'
|
2
|
+
|
3
|
+
module ElasticRecord
|
4
|
+
class Index
|
5
|
+
class ScrollEnumerator
|
6
|
+
attr_reader :keep_alive, :batch_size, :scroll_id
|
7
|
+
def initialize(elastic_index, search: nil, scroll_id: nil, keep_alive:, batch_size:)
|
8
|
+
@elastic_index = elastic_index
|
9
|
+
@search = search
|
10
|
+
@scroll_id = scroll_id
|
11
|
+
@keep_alive = keep_alive
|
12
|
+
@batch_size = batch_size
|
13
|
+
end
|
14
|
+
|
15
|
+
def each_slice(&block)
|
16
|
+
while (hits = request_more_hits.hits).any?
|
17
|
+
hits.each_slice(batch_size, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
@elastic_index.delete_scroll(scroll_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def request_more_ids
|
24
|
+
request_more_hits.to_ids
|
25
|
+
end
|
26
|
+
|
27
|
+
def request_more_hits
|
28
|
+
SearchHits.from_response(request_next_scroll)
|
29
|
+
end
|
30
|
+
|
31
|
+
def request_next_scroll
|
32
|
+
if scroll_id
|
33
|
+
response = @elastic_index.scroll(scroll_id, keep_alive)
|
34
|
+
|
35
|
+
if response['_scroll_id'] != scroll_id
|
36
|
+
@elastic_index.delete_scroll(scroll_id)
|
37
|
+
end
|
38
|
+
else
|
39
|
+
response = initial_search_response
|
40
|
+
end
|
41
|
+
|
42
|
+
@scroll_id = response['_scroll_id']
|
43
|
+
|
44
|
+
response
|
45
|
+
end
|
46
|
+
|
47
|
+
def total_hits
|
48
|
+
SearchHits.from_response(initial_search_response).total
|
49
|
+
end
|
50
|
+
|
51
|
+
def initial_search_response
|
52
|
+
@initial_search_response ||= begin
|
53
|
+
search_options = { size: batch_size, scroll: keep_alive }
|
54
|
+
elastic_query = @search.reverse_merge('sort' => '_doc')
|
55
|
+
|
56
|
+
@elastic_index.search(elastic_query, search_options)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module Search
|
62
|
+
def record_exists?(id)
|
63
|
+
get(id)['found']
|
64
|
+
end
|
65
|
+
|
66
|
+
def search(elastic_query, options = {})
|
67
|
+
url = "_search"
|
68
|
+
url += "?#{options.to_query}" if options.any?
|
69
|
+
|
70
|
+
get url, elastic_query
|
71
|
+
end
|
72
|
+
|
73
|
+
def multi_search(headers_and_bodies)
|
74
|
+
queries = headers_and_bodies.flat_map { |header, body| [header.to_json, body.to_json] }
|
75
|
+
queries = queries.join("\n") + "\n"
|
76
|
+
get "_msearch", queries
|
77
|
+
end
|
78
|
+
|
79
|
+
def explain(id, elastic_query)
|
80
|
+
get "_explain", elastic_query
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_scroll_enumerator(search: nil, scroll_id: nil, batch_size: 100, keep_alive: ElasticRecord::Config.scroll_keep_alive)
|
84
|
+
ScrollEnumerator.new(self, search: search, scroll_id: scroll_id, batch_size: batch_size, keep_alive: keep_alive)
|
85
|
+
end
|
86
|
+
|
87
|
+
def scroll(scroll_id, scroll_keep_alive)
|
88
|
+
options = {scroll_id: scroll_id, scroll: scroll_keep_alive}
|
89
|
+
connection.json_get("/_search/scroll?#{options.to_query}")
|
90
|
+
rescue ElasticRecord::ConnectionError => e
|
91
|
+
case e.status_code
|
92
|
+
when '400' then raise ElasticRecord::InvalidScrollError, e.message
|
93
|
+
when '404' then raise ElasticRecord::ExpiredScrollError, e.message
|
94
|
+
else raise e
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def delete_scroll(scroll_id)
|
99
|
+
connection.json_delete('/_search/scroll', { scroll_id: scroll_id })
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/elastic_record/model.rb
CHANGED
@@ -1,18 +1,16 @@
|
|
1
1
|
module ElasticRecord
|
2
2
|
module Model
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
singleton_class.delegate :query, :filter, :aggregate, to: :elastic_search
|
15
|
-
end
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
extend Searching
|
7
|
+
extend ClassMethods
|
8
|
+
extend FromSearchHit
|
9
|
+
include Callbacks
|
10
|
+
include AsDocument
|
11
|
+
|
12
|
+
singleton_class.delegate :query, :filter, :aggregate, to: :elastic_search
|
13
|
+
mattr_accessor :elastic_connection_cache, instance_writer: false
|
16
14
|
end
|
17
15
|
|
18
16
|
module ClassMethods
|
@@ -37,6 +35,10 @@ module ElasticRecord
|
|
37
35
|
def elastic_index=(index)
|
38
36
|
@elastic_index = index
|
39
37
|
end
|
38
|
+
|
39
|
+
def elastic_connection
|
40
|
+
self.elastic_connection_cache ||= ElasticRecord::Connection.new(ElasticRecord::Config.servers, ElasticRecord::Config.connection_options)
|
41
|
+
end
|
40
42
|
end
|
41
43
|
|
42
44
|
def index_to_elasticsearch
|
@@ -14,13 +14,25 @@ module ElasticRecord
|
|
14
14
|
|
15
15
|
attr_reader :klass, :values
|
16
16
|
|
17
|
-
def initialize(klass, values
|
17
|
+
def initialize(klass, values: {})
|
18
18
|
@klass = klass
|
19
19
|
@values = values
|
20
20
|
end
|
21
21
|
|
22
|
+
def initialize_copy(other)
|
23
|
+
@values = @values.dup
|
24
|
+
reset
|
25
|
+
end
|
26
|
+
|
27
|
+
def becomes(klass)
|
28
|
+
became = klass.allocate
|
29
|
+
became.instance_variable_set(:@klass, @klass)
|
30
|
+
became.instance_variable_set(:@values, @values.dup)
|
31
|
+
became
|
32
|
+
end
|
33
|
+
|
22
34
|
def count
|
23
|
-
|
35
|
+
search_hits.total
|
24
36
|
end
|
25
37
|
|
26
38
|
def aggregations
|
@@ -34,13 +46,16 @@ module ElasticRecord
|
|
34
46
|
klass.elastic_index.explain(id, as_elastic)
|
35
47
|
end
|
36
48
|
|
37
|
-
def
|
38
|
-
@
|
39
|
-
reset
|
49
|
+
def to_a
|
50
|
+
@records ||= find_hits(search_hits)
|
40
51
|
end
|
41
52
|
|
42
|
-
def
|
43
|
-
|
53
|
+
def find_hits(search_hits)
|
54
|
+
if klass.elastic_index.load_from_source
|
55
|
+
search_hits.hits.map { |hit| klass.from_search_hit(hit) }
|
56
|
+
else
|
57
|
+
klass.find search_hits.to_ids
|
58
|
+
end
|
44
59
|
end
|
45
60
|
|
46
61
|
def delete_all
|
@@ -9,7 +9,7 @@ module ElasticRecord
|
|
9
9
|
|
10
10
|
def find_in_batches(options = {})
|
11
11
|
find_hits_in_batches(options) do |hits|
|
12
|
-
yield hits
|
12
|
+
yield find_hits(hits)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -21,7 +21,7 @@ module ElasticRecord
|
|
21
21
|
|
22
22
|
def find_hits_in_batches(options = {})
|
23
23
|
build_scroll_enumerator(options).each_slice do |hits|
|
24
|
-
yield SearchHits.new(
|
24
|
+
yield SearchHits.new(hits)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -1,18 +1,21 @@
|
|
1
1
|
module ElasticRecord
|
2
2
|
class Relation
|
3
3
|
module Hits
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
4
6
|
def to_ids
|
5
7
|
search_hits.to_ids
|
6
8
|
end
|
7
9
|
|
8
10
|
def search_hits
|
9
|
-
SearchHits.from_response(
|
11
|
+
SearchHits.from_response(search_results)
|
10
12
|
end
|
11
13
|
|
12
14
|
def search_results
|
13
15
|
@search_results ||= begin
|
14
16
|
options = { typed_keys: true }
|
15
17
|
options[:search_type] = search_type_value if search_type_value
|
18
|
+
options[:_source] = klass.elastic_index.load_from_source
|
16
19
|
|
17
20
|
klass.elastic_index.search(as_elastic, options)
|
18
21
|
end
|
@@ -1,28 +1,20 @@
|
|
1
1
|
module ElasticRecord
|
2
2
|
class SearchHits
|
3
|
-
attr_accessor :hits, :model
|
3
|
+
attr_accessor :hits, :model, :total
|
4
4
|
|
5
5
|
class << self
|
6
|
-
def from_response(
|
7
|
-
new(
|
6
|
+
def from_response(response)
|
7
|
+
new(response['hits']['hits'], total: response['hits']['total'])
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
@model = model
|
11
|
+
def initialize(hits, total: nil)
|
13
12
|
@hits = hits
|
13
|
+
@total = total.is_a?(Hash) ? total['value'] : total
|
14
14
|
end
|
15
15
|
|
16
16
|
def to_ids
|
17
17
|
hits.map { |hit| hit['_id'] }
|
18
18
|
end
|
19
|
-
|
20
|
-
def to_records
|
21
|
-
if model.elastic_index.load_from_source
|
22
|
-
hits.map { |hit| model.from_search_hit(hit) }
|
23
|
-
else
|
24
|
-
model.find to_ids
|
25
|
-
end
|
26
|
-
end
|
27
19
|
end
|
28
20
|
end
|
@@ -21,6 +21,11 @@ class ElasticRecord::AsDocumentTest < MiniTest::Test
|
|
21
21
|
assert_equal 1, Widget.elastic_search.filter(color: 'grey').count
|
22
22
|
assert_equal 1, Widget.elastic_search.filter('widget_part.name' => 'Doohicky').count
|
23
23
|
assert_equal 0, Widget.elastic_search.filter(name: 'elmo').count
|
24
|
+
|
25
|
+
widget.widget_part = { name: nil }
|
26
|
+
widget.save!
|
27
|
+
|
28
|
+
assert_equal 1, Widget.elastic_search.filter('widget_part.name' => nil).count
|
24
29
|
end
|
25
30
|
|
26
31
|
class SpecialFieldsModel
|
@@ -17,14 +17,14 @@ class ElasticRecord::FromSearchHitsTest < MiniTest::Test
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_ranges
|
20
|
-
document = Project.elastic_relation.
|
20
|
+
document = Project.elastic_relation.first
|
21
21
|
|
22
22
|
assert_equal 'foo', document.name
|
23
23
|
assert_equal @project.estimated_start_date, document.estimated_start_date
|
24
24
|
end
|
25
25
|
|
26
26
|
def test_nested_ranges
|
27
|
-
document = Project.elastic_relation.
|
27
|
+
document = Project.elastic_relation.first
|
28
28
|
team_members = document.team_members.sort_by { |member| member['name'] }
|
29
29
|
|
30
30
|
assert_equal 26..29, team_members.first['estimated_age']
|
@@ -32,7 +32,7 @@ class ElasticRecord::FromSearchHitsTest < MiniTest::Test
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def test_object_ranges
|
35
|
-
document = Project.elastic_relation.
|
35
|
+
document = Project.elastic_relation.first
|
36
36
|
|
37
37
|
assert_equal 25..30, document.manager['estimated_age']
|
38
38
|
end
|
@@ -78,57 +78,6 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Test
|
|
78
78
|
assert index.record_exists?('joe')
|
79
79
|
end
|
80
80
|
|
81
|
-
def test_build_scroll_enumerator
|
82
|
-
index.index_document('bob', name: 'bob')
|
83
|
-
index.index_document('joe', name: 'joe')
|
84
|
-
|
85
|
-
scroll_enumerator = index.build_scroll_enumerator(search: {'query' => {query_string: {query: 'name:bob'}}})
|
86
|
-
|
87
|
-
assert_equal 1, scroll_enumerator.total_hits
|
88
|
-
assert_equal 1, scroll_enumerator.request_more_ids.size
|
89
|
-
end
|
90
|
-
|
91
|
-
def test_expired_scroll_error
|
92
|
-
index.index_document('bob', name: 'bob')
|
93
|
-
index.index_document('bobs', name: 'bob')
|
94
|
-
|
95
|
-
scroll_enumerator = index.build_scroll_enumerator(
|
96
|
-
search: { 'query' => { query_string: { query: 'name:bob' } } },
|
97
|
-
batch_size: 1,
|
98
|
-
keep_alive: '1ms'
|
99
|
-
)
|
100
|
-
|
101
|
-
scroll_enumerator.request_more_hits
|
102
|
-
index.delete_scroll(scroll_enumerator.scroll_id)
|
103
|
-
assert_raises ElasticRecord::ExpiredScrollError do
|
104
|
-
scroll_enumerator.request_more_hits
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def test_each_slice
|
109
|
-
10.times { |i| index.index_document("bob#{i}", color: 'red') }
|
110
|
-
batches = []
|
111
|
-
|
112
|
-
scroll_enumerator = index.build_scroll_enumerator(search: {'query' => {query_string: {query: 'color:red'}}}, batch_size: 1)
|
113
|
-
|
114
|
-
scroll_enumerator.each_slice do |slice|
|
115
|
-
batches << slice
|
116
|
-
end
|
117
|
-
|
118
|
-
assert_equal 10, batches.size
|
119
|
-
|
120
|
-
# Assert context was removed
|
121
|
-
assert_raises ElasticRecord::ExpiredScrollError do
|
122
|
-
scroll_enumerator.request_more_hits
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def test_invalid_scroll_error
|
127
|
-
assert_raises ElasticRecord::InvalidScrollError do
|
128
|
-
index.scroll('invalid', '1m')
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
81
|
def test_bulk
|
133
82
|
assert_nil index.current_bulk_batch
|
134
83
|
|
@@ -3,23 +3,21 @@ require 'helper'
|
|
3
3
|
class ElasticRecord::Index::MappingTest < MiniTest::Test
|
4
4
|
def test_get_mapping
|
5
5
|
expected = {
|
6
|
-
"
|
7
|
-
"
|
8
|
-
|
9
|
-
"
|
10
|
-
|
11
|
-
"
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
"
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
"
|
20
|
-
"
|
21
|
-
"name" => { "type" => "keyword" }
|
22
|
-
}
|
6
|
+
"properties" => {
|
7
|
+
"color" => { "type" => "keyword" },
|
8
|
+
"name" => {
|
9
|
+
"type" => "text",
|
10
|
+
"fields" => {
|
11
|
+
"raw" => { "type" => "keyword" }
|
12
|
+
}
|
13
|
+
},
|
14
|
+
"price" => {
|
15
|
+
"type" => "long"
|
16
|
+
},
|
17
|
+
"warehouse_id" => { "type" => "keyword" },
|
18
|
+
"widget_part" => {
|
19
|
+
"properties" => {
|
20
|
+
"name" => { "type" => "keyword" }
|
23
21
|
}
|
24
22
|
}
|
25
23
|
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class ElasticRecord::Index::SearchTest < MiniTest::Test
|
4
|
+
def test_build_scroll_enumerator
|
5
|
+
index.index_document('bob', name: 'bob')
|
6
|
+
index.index_document('joe', name: 'joe')
|
7
|
+
|
8
|
+
scroll_enumerator = index.build_scroll_enumerator(search: {'query' => {query_string: {query: 'name:bob'}}})
|
9
|
+
|
10
|
+
assert_equal 1, scroll_enumerator.total_hits
|
11
|
+
assert_equal 1, scroll_enumerator.request_more_ids.size
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_expired_scroll_error
|
15
|
+
index.index_document('bob', name: 'bob')
|
16
|
+
index.index_document('bobs', name: 'bob')
|
17
|
+
|
18
|
+
scroll_enumerator = index.build_scroll_enumerator(
|
19
|
+
search: { 'query' => { query_string: { query: 'name:bob' } } },
|
20
|
+
batch_size: 1,
|
21
|
+
keep_alive: '1ms'
|
22
|
+
)
|
23
|
+
|
24
|
+
scroll_enumerator.request_more_hits
|
25
|
+
index.delete_scroll(scroll_enumerator.scroll_id)
|
26
|
+
assert_raises ElasticRecord::ExpiredScrollError do
|
27
|
+
scroll_enumerator.request_more_hits
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_each_slice
|
32
|
+
10.times { |i| index.index_document("bob#{i}", color: 'red') }
|
33
|
+
batches = []
|
34
|
+
|
35
|
+
scroll_enumerator = index.build_scroll_enumerator(search: {'query' => {query_string: {query: 'color:red'}}}, batch_size: 1)
|
36
|
+
|
37
|
+
scroll_enumerator.each_slice do |slice|
|
38
|
+
batches << slice
|
39
|
+
end
|
40
|
+
|
41
|
+
assert_equal 10, batches.size
|
42
|
+
|
43
|
+
# Assert context was removed
|
44
|
+
assert_raises ElasticRecord::ExpiredScrollError do
|
45
|
+
scroll_enumerator.request_more_hits
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_invalid_scroll_error
|
50
|
+
assert_raises ElasticRecord::InvalidScrollError do
|
51
|
+
index.scroll('invalid', '1m')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def index
|
58
|
+
@index ||= Widget.elastic_index
|
59
|
+
end
|
60
|
+
end
|
@@ -5,22 +5,20 @@ class ElasticRecord::PercolatorModelTest < MiniTest::Test
|
|
5
5
|
index = WidgetQuery.elastic_index
|
6
6
|
|
7
7
|
expected_mapping = {
|
8
|
-
"
|
9
|
-
"
|
10
|
-
|
11
|
-
"
|
12
|
-
|
13
|
-
"
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
"
|
21
|
-
"
|
22
|
-
"name" => { "type" => "keyword" }
|
23
|
-
}
|
8
|
+
"properties"=> {
|
9
|
+
"color"=> {"type" => "keyword" },
|
10
|
+
"name"=> {
|
11
|
+
"type" => "text",
|
12
|
+
"fields" => {
|
13
|
+
"raw" => { "type" => "keyword" }
|
14
|
+
}
|
15
|
+
},
|
16
|
+
"price" => { "type" => "long" },
|
17
|
+
"query" => { "type" => "percolator" },
|
18
|
+
"warehouse_id" => { "type" => "keyword" },
|
19
|
+
"widget_part" => {
|
20
|
+
"properties" => {
|
21
|
+
"name" => { "type" => "keyword" }
|
24
22
|
}
|
25
23
|
}
|
26
24
|
}
|
@@ -16,14 +16,6 @@ class ElasticRecord::Relation::BatchesTest < MiniTest::Test
|
|
16
16
|
# assert_equal [@red_widget, @blue_widget, @green_widget].to_set, results.to_set
|
17
17
|
# end
|
18
18
|
|
19
|
-
def test_find_hits_in_batches
|
20
|
-
results = []
|
21
|
-
Widget.elastic_relation.find_hits_in_batches do |hits|
|
22
|
-
results << hits
|
23
|
-
end
|
24
|
-
assert_equal [[@red_widget, @blue_widget, @green_widget].to_set], results.map(&:to_records).map(&:to_set)
|
25
|
-
end
|
26
|
-
|
27
19
|
def test_find_ids_in_batches
|
28
20
|
results = []
|
29
21
|
Widget.elastic_relation.find_ids_in_batches do |ids|
|
@@ -1,6 +1,19 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class ElasticRecord::RelationTest < MiniTest::Test
|
4
|
+
class SpecialRelation < ElasticRecord::Relation
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_becomes
|
8
|
+
parent_relation = Widget.elastic_relation.filter(color: 'red')
|
9
|
+
became_relation = parent_relation.becomes(SpecialRelation)
|
10
|
+
|
11
|
+
assert_kind_of SpecialRelation, became_relation
|
12
|
+
assert_equal Widget, became_relation.klass
|
13
|
+
assert_equal parent_relation.values, became_relation.values
|
14
|
+
refute_equal parent_relation.values.object_id, became_relation.values.object_id
|
15
|
+
end
|
16
|
+
|
4
17
|
def test_count
|
5
18
|
original_count = Widget.elastic_relation.count
|
6
19
|
Widget.create(color: 'red')
|
@@ -14,18 +14,18 @@ class ElasticRecord::SearchingTest < MiniTest::Test
|
|
14
14
|
assert_equal widget, Widget.es.filter(color: 'red').first
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
-offset_value
|
22
|
-
end
|
17
|
+
class ScopedWidget < Widget
|
18
|
+
elastic_scope :by_color, ->(color) { elastic_search.filter(color: color) } do
|
19
|
+
def negative_offset
|
20
|
+
-offset_value
|
23
21
|
end
|
24
22
|
end
|
23
|
+
end
|
25
24
|
|
26
|
-
|
25
|
+
def test_elastic_scope
|
26
|
+
relation = ScopedWidget.by_color('blue')
|
27
27
|
|
28
|
-
assert_equal
|
28
|
+
assert_equal ScopedWidget.elastic_relation.filter(color: 'blue'), relation
|
29
29
|
assert_equal -5, relation.offset(5).negative_offset
|
30
30
|
end
|
31
31
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elastic_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Infogroup
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-
|
12
|
+
date: 2019-08-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: arelastic
|
@@ -75,6 +75,7 @@ files:
|
|
75
75
|
- lib/elastic_record/index/manage.rb
|
76
76
|
- lib/elastic_record/index/mapping.rb
|
77
77
|
- lib/elastic_record/index/mapping_type.rb
|
78
|
+
- lib/elastic_record/index/search.rb
|
78
79
|
- lib/elastic_record/index/settings.rb
|
79
80
|
- lib/elastic_record/log_subscriber.rb
|
80
81
|
- lib/elastic_record/lucene.rb
|
@@ -163,6 +164,7 @@ files:
|
|
163
164
|
- test/elastic_record/index/manage_test.rb
|
164
165
|
- test/elastic_record/index/mapping_test.rb
|
165
166
|
- test/elastic_record/index/mapping_type_test.rb
|
167
|
+
- test/elastic_record/index/search_test.rb
|
166
168
|
- test/elastic_record/index/settings_test.rb
|
167
169
|
- test/elastic_record/index_test.rb
|
168
170
|
- test/elastic_record/integration/active_record_test.rb
|
@@ -201,7 +203,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
201
203
|
- !ruby/object:Gem::Version
|
202
204
|
version: 1.8.11
|
203
205
|
requirements: []
|
204
|
-
|
206
|
+
rubyforge_project:
|
207
|
+
rubygems_version: 2.7.6
|
205
208
|
signing_key:
|
206
209
|
specification_version: 4
|
207
210
|
summary: An Elasticsearch querying ORM
|