elastic_record 5.5.0 → 5.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://secure.travis-ci.org/data-axle/elastic_record.png?rvm=2.0.0)](http://travis-ci.org/data-axle/elastic_record)
|
3
3
|
[![Code Climate](https://codeclimate.com/github/data-axle/elastic_record.png)](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
|