elastic_search_framework 2.1.0 → 2.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/lib/elastic_search_framework.rb +2 -0
- data/lib/elastic_search_framework/index.rb +27 -9
- data/lib/elastic_search_framework/index_alias.rb +161 -0
- data/lib/elastic_search_framework/repository.rb +30 -12
- data/lib/elastic_search_framework/sharded_index.rb +200 -0
- data/lib/elastic_search_framework/version.rb +1 -1
- data/spec/elastic_search_framework/index_alias_spec.rb +174 -0
- data/spec/elastic_search_framework/index_spec.rb +41 -11
- data/spec/elastic_search_framework/repository_spec.rb +34 -16
- data/spec/elastic_search_framework/sharded_index_spec.rb +348 -0
- data/spec/example_index_2.rb +35 -0
- data/spec/example_index_alias.rb +17 -0
- data/spec/example_index_alias_2.rb +8 -0
- data/spec/spec_helper.rb +3 -0
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 843e0d64a790e34d0cca88a47eb6824530b366a3f2f5732d09e8e7bae8437151
|
4
|
+
data.tar.gz: 914a7d0b4730df74b4cd8851d019f9cca83335951ba808b3b2a942d3398a0d8a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69b9c1c1e39841ad54cc1daf0b9895664ca4af635786dfc703c4ac8aa313af6146be873d26ca6358143f2bb1be11c453a28913a10437202524f8dbc4a75d93d9
|
7
|
+
data.tar.gz: e2c9a9d2ad91996c24837e2c7982b1e5a5f4a9a2d01c191e22a268f90fb9e8cbb63c2560b2cb5c105c9ac3c17dd930041bdda605512e389815e43747146f5677
|
@@ -8,6 +8,8 @@ require_relative 'elastic_search_framework/logger'
|
|
8
8
|
require_relative 'elastic_search_framework/exceptions'
|
9
9
|
require_relative 'elastic_search_framework/repository'
|
10
10
|
require_relative 'elastic_search_framework/index'
|
11
|
+
require_relative 'elastic_search_framework/index_alias'
|
12
|
+
require_relative 'elastic_search_framework/sharded_index'
|
11
13
|
require_relative 'elastic_search_framework/query'
|
12
14
|
|
13
15
|
module ElasticSearchFramework
|
@@ -2,14 +2,23 @@ module ElasticSearchFramework
|
|
2
2
|
module Index
|
3
3
|
attr_accessor :index_settings
|
4
4
|
|
5
|
-
def index(name:)
|
5
|
+
def index(name:, version: nil)
|
6
6
|
unless instance_variable_defined?(:@elastic_search_index_def)
|
7
|
-
instance_variable_set(:@elastic_search_index_def, name: "#{name}")
|
7
|
+
instance_variable_set(:@elastic_search_index_def, name: "#{name}#{version}")
|
8
|
+
instance_variable_set(:@elastic_search_index_version, version: version) unless version.nil?
|
8
9
|
else
|
9
10
|
raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Duplicate index description. Name: #{name}.")
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
14
|
+
def version
|
15
|
+
instance_variable_defined?(:@elastic_search_index_version) ? instance_variable_get(:@elastic_search_index_version) : 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def routing_enabled?
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
13
22
|
def id(field)
|
14
23
|
unless instance_variable_defined?(:@elastic_search_index_id)
|
15
24
|
instance_variable_set(:@elastic_search_index_id, field)
|
@@ -160,19 +169,28 @@ module ElasticSearchFramework
|
|
160
169
|
end
|
161
170
|
|
162
171
|
def repository
|
163
|
-
ElasticSearchFramework::Repository.new
|
172
|
+
@repository ||= ElasticSearchFramework::Repository.new
|
164
173
|
end
|
165
174
|
|
166
|
-
def get_item(id:, type: 'default')
|
167
|
-
|
175
|
+
def get_item(id:, type: 'default', routing_key: nil)
|
176
|
+
options = { index: self, id: id, type: type }
|
177
|
+
options[:routing_key] = routing_key if routing_enabled? && routing_key
|
178
|
+
|
179
|
+
repository.get(options)
|
168
180
|
end
|
169
181
|
|
170
|
-
def put_item(type: 'default', item:)
|
171
|
-
|
182
|
+
def put_item(type: 'default', item:, op_type: 'index', routing_key: nil)
|
183
|
+
options = { entity: item, index: self, type: type, op_type: op_type }
|
184
|
+
options[:routing_key] = routing_key if routing_enabled? && routing_key
|
185
|
+
|
186
|
+
repository.set(options)
|
172
187
|
end
|
173
188
|
|
174
|
-
def delete_item(id:, type: 'default')
|
175
|
-
|
189
|
+
def delete_item(id:, type: 'default', routing_key: nil)
|
190
|
+
options = { index: self, id: id, type: type }
|
191
|
+
options[:routing_key] = routing_key if routing_enabled? && routing_key
|
192
|
+
|
193
|
+
repository.drop(options)
|
176
194
|
end
|
177
195
|
|
178
196
|
def query
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module ElasticSearchFramework
|
2
|
+
module IndexAlias
|
3
|
+
def index(klass, active:)
|
4
|
+
unless instance_variable_defined?(:@elastic_search_indexes)
|
5
|
+
instance_variable_set(:@elastic_search_indexes, [])
|
6
|
+
end
|
7
|
+
indexes = self.instance_variable_get(:@elastic_search_indexes)
|
8
|
+
indexes << {klass: klass, active: active}
|
9
|
+
instance_variable_set(:@elastic_search_indexes, indexes)
|
10
|
+
end
|
11
|
+
|
12
|
+
def indexes
|
13
|
+
self.instance_variable_get(:@elastic_search_indexes)
|
14
|
+
end
|
15
|
+
|
16
|
+
def name(name)
|
17
|
+
unless instance_variable_defined?(:@elastic_search_index_alias_name)
|
18
|
+
instance_variable_set(:@elastic_search_index_alias_name, "#{name}")
|
19
|
+
else
|
20
|
+
raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Duplicate index alias name: #{name}.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid?
|
25
|
+
indexes.select { |i| i[:active] == true }.length == 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def create
|
29
|
+
if !valid?
|
30
|
+
raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Invalid Index alias.")
|
31
|
+
end
|
32
|
+
|
33
|
+
uri = URI("#{host}/_aliases")
|
34
|
+
|
35
|
+
payload = {
|
36
|
+
actions: [],
|
37
|
+
}
|
38
|
+
|
39
|
+
indexes.each do |index|
|
40
|
+
action = nil
|
41
|
+
if exists?(index: index[:klass])
|
42
|
+
action = "remove" if !index[:active]
|
43
|
+
else
|
44
|
+
action = "add" if index[:active]
|
45
|
+
end
|
46
|
+
next if action.nil?
|
47
|
+
|
48
|
+
payload[:actions] << {action => {index: index[:klass].full_name, alias: self.full_name}}
|
49
|
+
end
|
50
|
+
|
51
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
52
|
+
request.body = JSON.dump(payload)
|
53
|
+
request.content_type = "application/json"
|
54
|
+
|
55
|
+
response = repository.with_client do |client|
|
56
|
+
client.request(request)
|
57
|
+
end
|
58
|
+
|
59
|
+
is_valid_response?(response.code) || Integer(response.code) == 404
|
60
|
+
end
|
61
|
+
|
62
|
+
def delete
|
63
|
+
uri = URI("#{host}/_all/_aliases/#{full_name}")
|
64
|
+
|
65
|
+
request = Net::HTTP::Delete.new(uri.request_uri)
|
66
|
+
|
67
|
+
response = repository.with_client do |client|
|
68
|
+
client.request(request)
|
69
|
+
end
|
70
|
+
|
71
|
+
is_valid_response?(response.code) || Integer(response.code) == 404
|
72
|
+
end
|
73
|
+
|
74
|
+
def exists?(index:)
|
75
|
+
uri = URI("#{host}/#{index.full_name}/_alias/#{full_name}")
|
76
|
+
|
77
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
78
|
+
|
79
|
+
response = repository.with_client do |client|
|
80
|
+
client.request(request)
|
81
|
+
end
|
82
|
+
|
83
|
+
return false if response.code == "404"
|
84
|
+
|
85
|
+
result = nil
|
86
|
+
if is_valid_response?(response.code)
|
87
|
+
result = JSON.parse(response.body)
|
88
|
+
end
|
89
|
+
|
90
|
+
return true if !result.nil? && result[index.full_name]["aliases"] != nil
|
91
|
+
return false
|
92
|
+
end
|
93
|
+
|
94
|
+
def is_valid_response?(code)
|
95
|
+
[200, 201, 202].include?(Integer(code))
|
96
|
+
end
|
97
|
+
|
98
|
+
def full_name
|
99
|
+
name = instance_variable_get(:@elastic_search_index_alias_name)
|
100
|
+
if ElasticSearchFramework.namespace != nil
|
101
|
+
"#{ElasticSearchFramework.namespace}#{ElasticSearchFramework.namespace_delimiter}#{name.downcase}"
|
102
|
+
else
|
103
|
+
name.downcase
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def description
|
108
|
+
index = indexes.last[:klass]
|
109
|
+
hash = index.instance_variable_get(:@elastic_search_index_def)
|
110
|
+
if index.instance_variable_defined?(:@elastic_search_index_id)
|
111
|
+
hash[:id] = index.instance_variable_get(:@elastic_search_index_id)
|
112
|
+
else
|
113
|
+
hash[:id] = :id
|
114
|
+
end
|
115
|
+
hash
|
116
|
+
end
|
117
|
+
|
118
|
+
def host
|
119
|
+
"#{ElasticSearchFramework.host}:#{ElasticSearchFramework.port}"
|
120
|
+
end
|
121
|
+
|
122
|
+
def repository
|
123
|
+
@repository ||= ElasticSearchFramework::Repository.new
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_item(id:, type: "default", routing_key: nil)
|
127
|
+
active_index = indexes.detect { |i| i[:active] == true }[:klass]
|
128
|
+
|
129
|
+
options = { index: self, id: id, type: type }
|
130
|
+
options[:routing_key] = routing_key if active_index.routing_enabled? && routing_key
|
131
|
+
|
132
|
+
repository.get(options)
|
133
|
+
end
|
134
|
+
|
135
|
+
def put_item(type: "default", item:, op_type: 'index', routing_key: nil)
|
136
|
+
indexes.each do |index|
|
137
|
+
options = { entity: item, index: index[:klass], type: type, op_type: op_type }
|
138
|
+
options[:routing_key] = routing_key if index[:klass].routing_enabled? && routing_key
|
139
|
+
|
140
|
+
repository.set(options)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def delete_item(id:, type: "default", routing_key: nil)
|
145
|
+
indexes.each do |index|
|
146
|
+
options = { index: index[:klass], id: id, type: type }
|
147
|
+
options[:routing_key] = routing_key if index[:klass].routing_enabled? && routing_key
|
148
|
+
|
149
|
+
repository.drop(options)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def query
|
154
|
+
ElasticSearchFramework::Query.new(index: self)
|
155
|
+
end
|
156
|
+
|
157
|
+
def routing_enabled?
|
158
|
+
indexes.find(active: true).first[:klass].routing_enabled?
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -1,8 +1,11 @@
|
|
1
1
|
module ElasticSearchFramework
|
2
2
|
class Repository
|
3
3
|
|
4
|
-
def set(index:, entity:, type: 'default')
|
5
|
-
|
4
|
+
def set(index:, entity:, type: 'default', op_type: 'index', routing_key: nil)
|
5
|
+
uri_string = "#{host}/#{index.full_name}/#{type.downcase}/#{get_id_value(index: index, entity: entity)}?op_type=#{op_type}"
|
6
|
+
uri_string += "&routing=#{routing_key}" if routing_key
|
7
|
+
|
8
|
+
uri = URI(uri_string)
|
6
9
|
hash = hash_helper.to_hash(entity)
|
7
10
|
|
8
11
|
request = Net::HTTP::Put.new(uri.request_uri)
|
@@ -13,16 +16,22 @@ module ElasticSearchFramework
|
|
13
16
|
client.request(request)
|
14
17
|
end
|
15
18
|
|
16
|
-
|
19
|
+
if valid_response?(response.code)
|
20
|
+
return true
|
21
|
+
elsif op_type == 'create' && Integer(response.code) == 409
|
22
|
+
return true
|
23
|
+
else
|
17
24
|
raise ElasticSearchFramework::Exceptions::IndexError.new(
|
18
25
|
"An error occurred setting an index document. Response: #{response.body} | Code: #{response.code}"
|
19
26
|
)
|
20
27
|
end
|
21
|
-
return true
|
22
28
|
end
|
23
29
|
|
24
|
-
def get(index:, id:, type: 'default')
|
25
|
-
|
30
|
+
def get(index:, id:, type: 'default', routing_key: nil)
|
31
|
+
uri_string = "#{host}/#{index.full_name}/#{type.downcase}/#{id}/_source"
|
32
|
+
uri_string += "?routing=#{routing_key}" if routing_key
|
33
|
+
|
34
|
+
uri = URI(uri_string)
|
26
35
|
|
27
36
|
request = Net::HTTP::Get.new(uri.request_uri)
|
28
37
|
|
@@ -43,8 +52,11 @@ module ElasticSearchFramework
|
|
43
52
|
end
|
44
53
|
end
|
45
54
|
|
46
|
-
def drop(index:, id:, type: 'default')
|
47
|
-
|
55
|
+
def drop(index:, id:, type: 'default', routing_key: nil)
|
56
|
+
uri_string = "#{host}/#{index.full_name}/#{type.downcase}/#{id}"
|
57
|
+
uri_string += "?routing=#{routing_key}" if routing_key
|
58
|
+
|
59
|
+
uri = URI(uri_string)
|
48
60
|
|
49
61
|
request = Net::HTTP::Delete.new(uri.request_uri)
|
50
62
|
|
@@ -61,8 +73,11 @@ module ElasticSearchFramework
|
|
61
73
|
end
|
62
74
|
end
|
63
75
|
|
64
|
-
def query(index:, expression:, type: 'default', limit: 10, count: false)
|
65
|
-
|
76
|
+
def query(index:, expression:, type: 'default', limit: 10, count: false, routing_key: nil)
|
77
|
+
uri_string = "#{host}/#{index.full_name}/#{type}/_search?q=#{URI.encode(expression)}&size=#{limit}"
|
78
|
+
uri_string += "&routing=#{routing_key}" if routing_key
|
79
|
+
|
80
|
+
uri = URI(uri_string)
|
66
81
|
|
67
82
|
request = Net::HTTP::Get.new(uri.request_uri)
|
68
83
|
|
@@ -83,8 +98,11 @@ module ElasticSearchFramework
|
|
83
98
|
end
|
84
99
|
end
|
85
100
|
|
86
|
-
def json_query(index_name:, json_query:, type: 'default')
|
87
|
-
|
101
|
+
def json_query(index_name:, json_query:, type: 'default', routing_key: nil)
|
102
|
+
uri_string = "#{host}/#{index_name}/#{type}/_search"
|
103
|
+
uri_string += "?routing=#{routing_key}" if routing_key
|
104
|
+
|
105
|
+
uri = URI(uri_string)
|
88
106
|
|
89
107
|
request = Net::HTTP::Get.new(uri.request_uri)
|
90
108
|
request.content_type = 'application/json'
|
@@ -0,0 +1,200 @@
|
|
1
|
+
module ElasticSearchFramework
|
2
|
+
module ShardedIndex
|
3
|
+
attr_accessor :index_settings
|
4
|
+
|
5
|
+
def index(name:, version: nil)
|
6
|
+
unless instance_variable_defined?(:@elastic_search_index_def)
|
7
|
+
instance_variable_set(:@elastic_search_index_def, name: "#{name}#{version}")
|
8
|
+
instance_variable_set(:@elastic_search_index_version, version: version) unless version.nil?
|
9
|
+
else
|
10
|
+
raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Duplicate index description. Name: #{name}.")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def version
|
15
|
+
instance_variable_defined?(:@elastic_search_index_version) ? instance_variable_get(:@elastic_search_index_version) : 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def routing_enabled?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def id(field)
|
23
|
+
unless instance_variable_defined?(:@elastic_search_index_id)
|
24
|
+
instance_variable_set(:@elastic_search_index_id, field)
|
25
|
+
else
|
26
|
+
raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Duplicate index id. Field: #{field}.")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def mapping(name:, field:, **options)
|
31
|
+
unless instance_variable_defined?(:@elastic_search_index_mappings)
|
32
|
+
instance_variable_set(:@elastic_search_index_mappings, {})
|
33
|
+
end
|
34
|
+
|
35
|
+
mappings = instance_variable_get(:@elastic_search_index_mappings)
|
36
|
+
|
37
|
+
mappings[name] = {} if mappings[name].nil?
|
38
|
+
|
39
|
+
mappings[name][field] = options
|
40
|
+
|
41
|
+
instance_variable_set(:@elastic_search_index_mappings, mappings)
|
42
|
+
end
|
43
|
+
|
44
|
+
def create
|
45
|
+
if !valid?
|
46
|
+
raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Invalid Index description specified.")
|
47
|
+
end
|
48
|
+
|
49
|
+
if exists?
|
50
|
+
ElasticSearchFramework.logger.debug { "[#{self.class}] - Index already exists."}
|
51
|
+
return
|
52
|
+
end
|
53
|
+
payload = create_payload
|
54
|
+
|
55
|
+
put(payload: payload)
|
56
|
+
end
|
57
|
+
|
58
|
+
def put(payload:)
|
59
|
+
uri = URI("#{host}/#{full_name}")
|
60
|
+
|
61
|
+
request = Net::HTTP::Put.new(uri.request_uri)
|
62
|
+
request.body = JSON.dump(payload)
|
63
|
+
request.content_type = 'application/json'
|
64
|
+
|
65
|
+
response = repository.with_client do |client|
|
66
|
+
client.request(request)
|
67
|
+
end
|
68
|
+
|
69
|
+
unless is_valid_response?(response.code)
|
70
|
+
if JSON.parse(response.body, symbolize_names: true).dig(:error, :root_cause, 0, :type) == 'index_already_exists_exception'
|
71
|
+
# We get here because the `exists?` check in #create is non-atomic
|
72
|
+
ElasticSearchFramework.logger.warn "[#{self.class}] - Failed to create preexisting index. | Response: #{response.body}"
|
73
|
+
else
|
74
|
+
raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self}] - Failed to put index. Payload: #{payload} | Response: #{response.body}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
def get
|
81
|
+
uri = URI("#{host}/#{full_name}")
|
82
|
+
|
83
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
84
|
+
|
85
|
+
response = repository.with_client do |client|
|
86
|
+
client.request(request)
|
87
|
+
end
|
88
|
+
|
89
|
+
result = nil
|
90
|
+
if is_valid_response?(response.code)
|
91
|
+
result = JSON.parse(response.body)
|
92
|
+
end
|
93
|
+
result
|
94
|
+
end
|
95
|
+
|
96
|
+
def exists?
|
97
|
+
get != nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def delete
|
101
|
+
uri = URI("#{host}/#{full_name}")
|
102
|
+
|
103
|
+
request = Net::HTTP::Delete.new(uri.request_uri)
|
104
|
+
|
105
|
+
response = repository.with_client do |client|
|
106
|
+
client.request(request)
|
107
|
+
end
|
108
|
+
|
109
|
+
is_valid_response?(response.code) || Integer(response.code) == 404
|
110
|
+
end
|
111
|
+
|
112
|
+
def settings(name:, type: nil, value:)
|
113
|
+
self.index_settings = {} if index_settings.nil?
|
114
|
+
index_settings[name] = {} if index_settings[name].nil?
|
115
|
+
return index_settings[name][type] = value if type
|
116
|
+
index_settings[name] = value
|
117
|
+
end
|
118
|
+
|
119
|
+
def create_payload
|
120
|
+
payload = { }
|
121
|
+
payload[:settings] = index_settings unless index_settings.nil?
|
122
|
+
|
123
|
+
unless mappings.keys.empty?
|
124
|
+
payload[:mappings] = {}
|
125
|
+
|
126
|
+
mappings.keys.each do |name|
|
127
|
+
payload[:mappings][name] = { properties: {} }
|
128
|
+
mappings[name].keys.each do |field|
|
129
|
+
payload[:mappings][name][:properties][field] = mappings[name][field]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
payload
|
135
|
+
end
|
136
|
+
|
137
|
+
def valid?
|
138
|
+
self.instance_variable_get(:@elastic_search_index_def) ? true : false
|
139
|
+
end
|
140
|
+
|
141
|
+
def description
|
142
|
+
hash = self.instance_variable_get(:@elastic_search_index_def)
|
143
|
+
if instance_variable_defined?(:@elastic_search_index_id)
|
144
|
+
hash[:id] = self.instance_variable_get(:@elastic_search_index_id)
|
145
|
+
else
|
146
|
+
hash[:id] = :id
|
147
|
+
end
|
148
|
+
hash
|
149
|
+
end
|
150
|
+
|
151
|
+
def mappings
|
152
|
+
self.instance_variable_defined?(:@elastic_search_index_mappings) ? self.instance_variable_get(:@elastic_search_index_mappings) : {}
|
153
|
+
end
|
154
|
+
|
155
|
+
def is_valid_response?(code)
|
156
|
+
[200,201,202].include?(Integer(code))
|
157
|
+
end
|
158
|
+
|
159
|
+
def full_name
|
160
|
+
if ElasticSearchFramework.namespace != nil
|
161
|
+
"#{ElasticSearchFramework.namespace}#{ElasticSearchFramework.namespace_delimiter}#{description[:name].downcase}"
|
162
|
+
else
|
163
|
+
description[:name].downcase
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def host
|
168
|
+
"#{ElasticSearchFramework.host}:#{ElasticSearchFramework.port}"
|
169
|
+
end
|
170
|
+
|
171
|
+
def repository
|
172
|
+
@repository ||= ElasticSearchFramework::Repository.new
|
173
|
+
end
|
174
|
+
|
175
|
+
def get_item(id:, type: 'default', routing_key: nil)
|
176
|
+
options = { index: self, id: id, type: type }
|
177
|
+
options[:routing_key] = routing_key if routing_enabled? && routing_key
|
178
|
+
|
179
|
+
repository.get(options)
|
180
|
+
end
|
181
|
+
|
182
|
+
def put_item(type: 'default', item:, op_type: 'index', routing_key: nil)
|
183
|
+
options = { entity: item, index: self, type: type, op_type: op_type }
|
184
|
+
options[:routing_key] = routing_key if routing_enabled? && routing_key
|
185
|
+
|
186
|
+
repository.set(options)
|
187
|
+
end
|
188
|
+
|
189
|
+
def delete_item(id:, type: 'default', routing_key: nil)
|
190
|
+
options = { index: self, id: id, type: type }
|
191
|
+
options[:routing_key] = routing_key if routing_enabled? && routing_key
|
192
|
+
|
193
|
+
repository.drop(options)
|
194
|
+
end
|
195
|
+
|
196
|
+
def query
|
197
|
+
ElasticSearchFramework::Query.new(index: self)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
RSpec.describe ElasticSearchFramework::Index do
|
2
|
+
describe '#full_name' do
|
3
|
+
let(:namespace) { 'uat' }
|
4
|
+
let(:namespace_delimiter) { '.' }
|
5
|
+
before do
|
6
|
+
ElasticSearchFramework.namespace = namespace
|
7
|
+
ElasticSearchFramework.namespace_delimiter = namespace_delimiter
|
8
|
+
end
|
9
|
+
it 'should return the full index name including namespace and delimiter' do
|
10
|
+
expect(ExampleIndexAlias.full_name).to eq "#{ElasticSearchFramework.namespace}#{ElasticSearchFramework.namespace_delimiter}example"
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when the namespace is nil' do
|
14
|
+
before { ElasticSearchFramework.namespace = nil }
|
15
|
+
|
16
|
+
it 'returns the description name downcased' do
|
17
|
+
expect(ExampleIndexAlias.full_name).to eq 'example'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#valid?' do
|
23
|
+
context 'for a valid index definition' do
|
24
|
+
it 'should return true' do
|
25
|
+
expect(ExampleIndexAlias.valid?).to be true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
context 'for an invalid index definition' do
|
29
|
+
it 'should return true' do
|
30
|
+
expect(InvalidIndexAlias.valid?).to be false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#create' do
|
36
|
+
context 'when alias is valid and does not exist' do
|
37
|
+
before do
|
38
|
+
ExampleIndexAlias.delete
|
39
|
+
ExampleIndex.delete if ExampleIndex.exists?
|
40
|
+
ExampleIndex.create
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'creates an alias for the active index' do
|
44
|
+
expect(ExampleIndexAlias.exists?(index: ExampleIndex)).to be false
|
45
|
+
ExampleIndexAlias.create
|
46
|
+
expect(ExampleIndexAlias.exists?(index: ExampleIndex)).to be true
|
47
|
+
end
|
48
|
+
|
49
|
+
after do
|
50
|
+
ExampleIndexAlias.delete
|
51
|
+
ExampleIndex.delete
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when alias is not valid' do
|
56
|
+
before { allow(ExampleIndexAlias).to receive(:valid?).and_return(false) }
|
57
|
+
|
58
|
+
it 'raises an error' do
|
59
|
+
expect(ExampleIndexAlias.exists?(index: ExampleIndex)).to be false
|
60
|
+
expect { ExampleIndexAlias.create }.to raise_error(
|
61
|
+
ElasticSearchFramework::Exceptions::IndexError
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when alias is valid but already exists' do
|
67
|
+
before do
|
68
|
+
ExampleIndex.delete if ExampleIndex.exists?
|
69
|
+
ExampleIndex.create
|
70
|
+
ExampleIndexAlias.create
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'does not try to create a new alias' do
|
74
|
+
ExampleIndexAlias.create
|
75
|
+
end
|
76
|
+
|
77
|
+
after do
|
78
|
+
ExampleIndexAlias.delete
|
79
|
+
ExampleIndex.delete
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when alias is valid and does not exist and requires updating' do
|
84
|
+
before do
|
85
|
+
ExampleIndexAlias.delete
|
86
|
+
ExampleIndex.delete if ExampleIndex.exists?
|
87
|
+
ExampleIndex2.delete if ExampleIndex2.exists?
|
88
|
+
ExampleIndex.create
|
89
|
+
ExampleIndex2.create
|
90
|
+
ExampleIndexAlias.create
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'modifies the alias to the active index' do
|
94
|
+
expect(ExampleIndexAlias.exists?(index: ExampleIndex)).to be true
|
95
|
+
ExampleIndexAlias2.create
|
96
|
+
expect(ExampleIndexAlias.exists?(index: ExampleIndex)).to be false
|
97
|
+
expect(ExampleIndexAlias2.exists?(index: ExampleIndex2)).to be true
|
98
|
+
end
|
99
|
+
|
100
|
+
after do
|
101
|
+
ExampleIndexAlias.delete
|
102
|
+
ExampleIndex.delete
|
103
|
+
ExampleIndex2.delete
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#get_item' do
|
109
|
+
let(:id) { 10 }
|
110
|
+
let(:type) { 'default' }
|
111
|
+
context 'when active index routing_enabled false' do
|
112
|
+
it 'should call get on the repository' do
|
113
|
+
expect(ExampleIndexAlias.repository).to receive(:get).with(index: ExampleIndexAlias, id: id, type: type).once
|
114
|
+
ExampleIndexAlias.get_item(id: id, type: type)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should call get on the repository and not pass through routing_key when supplied' do
|
118
|
+
expect(ExampleIndexAlias.repository).to receive(:get).with(index: ExampleIndexAlias, id: id, type: type).once
|
119
|
+
ExampleIndexAlias.get_item(id: id, type: type, routing_key: 5)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'when active index routing_enabled true' do
|
124
|
+
it 'should call get on the repository' do
|
125
|
+
expect(ExampleIndexAlias2.repository).to receive(:get).with(index: ExampleIndexAlias2, id: id, type: type, routing_key: 5).once
|
126
|
+
ExampleIndexAlias2.get_item(id: id, type: type, routing_key: 5)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#put_item' do
|
132
|
+
let(:id) { 10 }
|
133
|
+
let(:type) { 'default' }
|
134
|
+
let(:item) do
|
135
|
+
TestItem.new.tap do |i|
|
136
|
+
i.id = id
|
137
|
+
i.name = 'abc'
|
138
|
+
i.timestamp = Time.now.to_i
|
139
|
+
i.number = 5
|
140
|
+
end
|
141
|
+
end
|
142
|
+
context 'without specifying op_type' do
|
143
|
+
it 'should call set on the repository for each index of the alias with default op_type (index)' do
|
144
|
+
expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex, type: type, op_type: 'index').once
|
145
|
+
expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex2, type: type, op_type: 'index', routing_key: 5).once
|
146
|
+
ExampleIndexAlias.put_item(type: type, item: item, routing_key: 5)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'with specified op_type' do
|
151
|
+
it 'should call set on the repository for each index of the alias with supplied op_type (index)' do
|
152
|
+
expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex, type: type, op_type: 'index').once
|
153
|
+
expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex2, type: type, op_type: 'index', routing_key: 5).once
|
154
|
+
ExampleIndexAlias.put_item(type: type, item: item, op_type: 'index', routing_key: 5)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should call set on the repository for each index of the alias with supplied op_type (create)' do
|
158
|
+
expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex, type: type, op_type: 'create').once
|
159
|
+
expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex2, type: type, op_type: 'create', routing_key: 5).once
|
160
|
+
ExampleIndexAlias.put_item(type: type, item: item, op_type: 'create', routing_key: 5)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe '#delete_item' do
|
166
|
+
let(:id) { 10 }
|
167
|
+
let(:type) { 'default' }
|
168
|
+
it 'should call drop on the repository for each index of the alias' do
|
169
|
+
expect(ExampleIndexAlias.repository).to receive(:drop).with(index: ExampleIndex, id: id, type: type).once
|
170
|
+
expect(ExampleIndexAlias.repository).to receive(:drop).with(index: ExampleIndex2, id: id, type: type, routing_key: 5).once
|
171
|
+
ExampleIndexAlias.delete_item(id: id, type: type, routing_key: 5)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -79,17 +79,18 @@ RSpec.describe ElasticSearchFramework::Index do
|
|
79
79
|
{
|
80
80
|
'normalizer' => {
|
81
81
|
'custom_normalizer' => {
|
82
|
+
'char_filter' => [],
|
82
83
|
'filter' => ['lowercase'],
|
83
84
|
'type' => 'custom'
|
84
85
|
}
|
85
86
|
},
|
86
|
-
'analyzer'=>{
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
}
|
87
|
+
'analyzer' => {
|
88
|
+
'custom_analyzer' => {
|
89
|
+
'filter' => ['lowercase'],
|
90
|
+
'type' => 'custom',
|
91
|
+
'tokenizer' => 'standard'
|
92
92
|
}
|
93
|
+
}
|
93
94
|
}
|
94
95
|
end
|
95
96
|
|
@@ -276,8 +277,8 @@ RSpec.describe ElasticSearchFramework::Index do
|
|
276
277
|
it 'should return a ElasticSearchFramework::Repository instance' do
|
277
278
|
expect(ExampleIndex.repository).to be_a(ElasticSearchFramework::Repository)
|
278
279
|
end
|
279
|
-
it 'should return
|
280
|
-
expect(ExampleIndex.repository).
|
280
|
+
it 'should return the same ElasticSearchFramework::Repository instance for multiple calls' do
|
281
|
+
expect(ExampleIndex.repository).to eq ExampleIndex.repository
|
281
282
|
end
|
282
283
|
end
|
283
284
|
|
@@ -301,9 +302,38 @@ RSpec.describe ElasticSearchFramework::Index do
|
|
301
302
|
i.number = 5
|
302
303
|
end
|
303
304
|
end
|
304
|
-
|
305
|
-
|
306
|
-
|
305
|
+
|
306
|
+
context 'without specifying op_type' do
|
307
|
+
it 'should call set on the repository with default op_type (index)' do
|
308
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:set).with(entity: item, index: ExampleIndex, op_type: 'index', type: type)
|
309
|
+
ExampleIndex.put_item(type: type, item: item)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
context 'with specified op_type' do
|
314
|
+
it 'should call set on the repository with supplied op_type (index)' do
|
315
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:set).with(entity: item, index: ExampleIndex, op_type: 'index', type: type)
|
316
|
+
ExampleIndex.put_item(type: type, item: item, op_type: 'index')
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'should call set on the repository with supplied op_type (create)' do
|
320
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:set).with(entity: item, index: ExampleIndex, op_type: 'create', type: type)
|
321
|
+
ExampleIndex.put_item(type: type, item: item, op_type: 'create')
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context 'without specifying routing_key' do
|
326
|
+
it 'should call set on the repository without routing_key' do
|
327
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:set).with(entity: item, index: ExampleIndex, op_type: 'index', type: type)
|
328
|
+
ExampleIndex.put_item(type: type, item: item, op_type: 'index')
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
context 'with specified routing_key' do
|
333
|
+
it 'should call set on the repository without routing_key' do
|
334
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:set).with(entity: item, index: ExampleIndex, op_type: 'index', type: type)
|
335
|
+
ExampleIndex.put_item(type: type, item: item, op_type: 'index', routing_key: '6')
|
336
|
+
end
|
307
337
|
end
|
308
338
|
end
|
309
339
|
|
@@ -63,22 +63,40 @@ RSpec.describe ElasticSearchFramework::Repository do
|
|
63
63
|
ExampleIndexWithId.create
|
64
64
|
end
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
66
|
+
context 'PUT with op_type: index' do
|
67
|
+
it 'should create, read and delete an index document' do
|
68
|
+
subject.set(index: ExampleIndex, entity: item1)
|
69
|
+
subject.set(index: ExampleIndex, entity: item1.tap { |i| i.timestamp += 100 })
|
70
|
+
subject.set(index: ExampleIndex, entity: item1.tap { |i| i.timestamp += 100 }, op_type: 'index')
|
71
|
+
subject.set(index: ExampleIndex, entity: item2)
|
72
|
+
subject.set(index: ExampleIndex, entity: item5)
|
73
|
+
index_item1 = subject.get(index: ExampleIndex, id: item1.id)
|
74
|
+
expect(index_item1[:id]).to eq item1.id
|
75
|
+
expect(index_item1[:name]).to eq item1.name
|
76
|
+
expect(index_item1[:timestamp]).to eq item1.timestamp
|
77
|
+
expect(index_item1[:number]).to eq item1.number
|
78
|
+
index_item2 = subject.get(index: ExampleIndex, id: item2.id)
|
79
|
+
expect(index_item2[:id]).to eq item2.id
|
80
|
+
expect(index_item2[:name]).to eq item2.name
|
81
|
+
expect(index_item2[:timestamp]).to eq item2.timestamp
|
82
|
+
expect(index_item2[:number]).to eq item2.number
|
83
|
+
subject.drop(index: ExampleIndex, id: item1.id)
|
84
|
+
expect(subject.get(index: ExampleIndex, id: item1.id)).to be_nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'PUT with op_type: create' do
|
89
|
+
let!(:original_timestamp) { item1.timestamp }
|
90
|
+
|
91
|
+
it 'should not update item' do
|
92
|
+
subject.set(index: ExampleIndex, entity: item1)
|
93
|
+
subject.set(index: ExampleIndex, entity: item1.tap { |i| i.timestamp += 100 }, op_type: 'create')
|
94
|
+
index_item1 = subject.get(index: ExampleIndex, id: item1.id)
|
95
|
+
expect(index_item1[:id]).to eq item1.id
|
96
|
+
expect(index_item1[:name]).to eq item1.name
|
97
|
+
expect(index_item1[:timestamp]).to eq original_timestamp
|
98
|
+
expect(index_item1[:number]).to eq item1.number
|
99
|
+
end
|
82
100
|
end
|
83
101
|
|
84
102
|
after do
|
@@ -0,0 +1,348 @@
|
|
1
|
+
RSpec.describe ElasticSearchFramework::ShardedIndex do
|
2
|
+
describe '#description' do
|
3
|
+
it 'should return the index details' do
|
4
|
+
expect(ExampleIndex2.description).to be_a(Hash)
|
5
|
+
expect(ExampleIndex2.description[:name]).to eq 'example_index2'
|
6
|
+
expect(ExampleIndex2.description[:id]).to eq :id
|
7
|
+
expect(ExampleIndexWithId2.description[:id]).to eq :number
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#index' do
|
12
|
+
before { ExampleIndex2.create unless ExampleIndex2.exists? }
|
13
|
+
context 'when the instance variable is not defined' do
|
14
|
+
before { allow(ExampleIndex2).to receive(:instance_variable_defined?).and_return(true) }
|
15
|
+
it 'raises an index error' do
|
16
|
+
expect { ExampleIndex2.index(name: 'test') }.to raise_error(
|
17
|
+
ElasticSearchFramework::Exceptions::IndexError,
|
18
|
+
'[Class] - Duplicate index description. Name: test.'
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#id' do
|
25
|
+
context 'when the instance variable is not defined' do
|
26
|
+
before { allow(ExampleIndex2).to receive(:instance_variable_defined?).and_return(true) }
|
27
|
+
it 'raises an index error' do
|
28
|
+
expect { ExampleIndex2.id('name') }.to raise_error(
|
29
|
+
ElasticSearchFramework::Exceptions::IndexError,
|
30
|
+
"[Class] - Duplicate index id. Field: name."
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#full_name' do
|
37
|
+
let(:namespace) { 'uat' }
|
38
|
+
let(:namespace_delimiter) { '.' }
|
39
|
+
before do
|
40
|
+
ElasticSearchFramework.namespace = namespace
|
41
|
+
ElasticSearchFramework.namespace_delimiter = namespace_delimiter
|
42
|
+
end
|
43
|
+
it 'should return the full index name including namespace and delimiter' do
|
44
|
+
expect(ExampleIndex2.full_name).to eq "#{ElasticSearchFramework.namespace}#{ElasticSearchFramework.namespace_delimiter}#{ExampleIndex2.description[:name]}"
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when the namespace is nil' do
|
48
|
+
before { ElasticSearchFramework.namespace = nil }
|
49
|
+
|
50
|
+
it 'returns the description name downcased' do
|
51
|
+
expect(ExampleIndex2.full_name).to eq 'example_index2'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#mapping' do
|
57
|
+
it 'should add mapping details to the index definition' do
|
58
|
+
mappings = ExampleIndex2.mappings
|
59
|
+
expect(mappings).to be_a(Hash)
|
60
|
+
expect(mappings.length).to eq 1
|
61
|
+
expect(mappings['default'][:name][:type]).to eq :keyword
|
62
|
+
expect(mappings['default'][:name][:index]).to eq true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#analysis' do
|
67
|
+
context 'when analysis is nil' do
|
68
|
+
before { ExampleIndex2.delete if ExampleIndex2.exists? }
|
69
|
+
|
70
|
+
it 'does not add analysis to the index' do
|
71
|
+
ExampleIndex2.create
|
72
|
+
expect(ExampleIndexWithSettings2.get.dig('example_index2', 'settings', 'index', 'analysis')).to be_nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when analysis is not nil' do
|
77
|
+
before { ExampleIndexWithSettings2.delete if ExampleIndexWithSettings2.exists? }
|
78
|
+
let(:expected) do
|
79
|
+
{
|
80
|
+
'normalizer' => {
|
81
|
+
'custom_normalizer' => {
|
82
|
+
'char_filter' => [],
|
83
|
+
'filter' => ['lowercase'],
|
84
|
+
'type' => 'custom'
|
85
|
+
}
|
86
|
+
},
|
87
|
+
'analyzer' => {
|
88
|
+
'custom_analyzer' => {
|
89
|
+
'filter' => ['lowercase'],
|
90
|
+
'type' => 'custom',
|
91
|
+
'tokenizer' => 'standard'
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'adds analysis to the index' do
|
98
|
+
ExampleIndexWithSettings2.create
|
99
|
+
expect(ExampleIndexWithSettings2.get.dig('example_index2', 'settings', 'index', 'number_of_shards')).to eq('1')
|
100
|
+
expect(ExampleIndexWithSettings2.get.dig('example_index2', 'settings', 'index', 'analysis')).to eq(expected)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '#valid?' do
|
106
|
+
context 'for a valid index definition' do
|
107
|
+
it 'should return true' do
|
108
|
+
expect(ExampleIndex2.valid?).to be true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
context 'for an invalid index definition' do
|
112
|
+
it 'should return true' do
|
113
|
+
expect(InvalidExampleIndex2.valid?).to be false
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#create' do
|
119
|
+
context 'when index is valid and does not exist' do
|
120
|
+
before { ExampleIndex2.delete if ExampleIndex2.exists? }
|
121
|
+
|
122
|
+
it 'should create an index' do
|
123
|
+
expect(ExampleIndex2.exists?).to be false
|
124
|
+
ExampleIndex2.create
|
125
|
+
expect(ExampleIndex2.exists?).to be true
|
126
|
+
end
|
127
|
+
|
128
|
+
after do
|
129
|
+
ExampleIndex2.delete
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'when index is not valid' do
|
134
|
+
before { allow(ExampleIndex2).to receive(:valid?).and_return(false) }
|
135
|
+
|
136
|
+
it 'raises an error' do
|
137
|
+
expect(ExampleIndex2.exists?).to be false
|
138
|
+
expect { ExampleIndex2.create }.to raise_error(
|
139
|
+
ElasticSearchFramework::Exceptions::IndexError,
|
140
|
+
'[Class] - Invalid Index description specified.'
|
141
|
+
)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'when index is valid but already exists' do
|
146
|
+
before { ExampleIndex2.delete if ExampleIndex2.exists? }
|
147
|
+
|
148
|
+
it 'does not try to create a new index' do
|
149
|
+
allow(ExampleIndex2).to receive(:exists?).and_return(true)
|
150
|
+
expect(ExampleIndex2).not_to receive(:put)
|
151
|
+
ExampleIndex2.create
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe '#delete' do
|
157
|
+
before do
|
158
|
+
unless ExampleIndex2.exists?
|
159
|
+
ExampleIndex2.create
|
160
|
+
end
|
161
|
+
end
|
162
|
+
it 'should create an index' do
|
163
|
+
expect(ExampleIndex2.exists?).to be true
|
164
|
+
ExampleIndex2.delete
|
165
|
+
expect(ExampleIndex2.exists?).to be false
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe '#exists?' do
|
170
|
+
context 'when an index exists' do
|
171
|
+
before do
|
172
|
+
ExampleIndex2.delete
|
173
|
+
ExampleIndex2.create
|
174
|
+
end
|
175
|
+
it 'should return true' do
|
176
|
+
expect(ExampleIndex2.exists?).to be true
|
177
|
+
end
|
178
|
+
end
|
179
|
+
context 'when an index exists' do
|
180
|
+
before do
|
181
|
+
ExampleIndex2.delete
|
182
|
+
end
|
183
|
+
it 'should return false' do
|
184
|
+
expect(ExampleIndex2.exists?).to be false
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe '#put' do
|
190
|
+
let(:payload) { {} }
|
191
|
+
context 'when there is a valid response' do
|
192
|
+
before { allow(ExampleIndex2).to receive(:is_valid_response?).and_return(true) }
|
193
|
+
|
194
|
+
it 'returns true' do
|
195
|
+
ExampleIndex2.create
|
196
|
+
expect(ExampleIndex2.put(payload: payload)).to eq true
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'when there is not a valid response' do
|
201
|
+
before { allow(ExampleIndex2).to receive(:is_valid_response?).and_return(false) }
|
202
|
+
|
203
|
+
context 'when the error is "index_already_exists_exception"' do
|
204
|
+
let(:response_body) { { error: { root_cause: [{ type: 'index_already_exists_exception' }] } } }
|
205
|
+
let(:request) { double }
|
206
|
+
|
207
|
+
before { ExampleIndex2.delete if ExampleIndex2.exists? }
|
208
|
+
it 'returns true' do
|
209
|
+
allow(request).to receive(:body).and_return(response_body.to_json)
|
210
|
+
allow(request).to receive(:code).and_return(404)
|
211
|
+
allow_any_instance_of(Net::HTTP).to receive(:request).and_return(request)
|
212
|
+
expect(ExampleIndex2.put(payload: payload)).to eq true
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context 'when the error is not "index_already_exists_exception"' do
|
217
|
+
let(:response_body) { { error: { root_cause: [{ type: 'foo' }] } } }
|
218
|
+
let(:request) { double }
|
219
|
+
it 'raises an IndexError' do
|
220
|
+
allow(request).to receive(:body).and_return(response_body.to_json)
|
221
|
+
allow(request).to receive(:code).and_return(404)
|
222
|
+
allow_any_instance_of(Net::HTTP).to receive(:request).and_return(request)
|
223
|
+
expect { ExampleIndex2.put(payload: payload) }.to raise_error(
|
224
|
+
ElasticSearchFramework::Exceptions::IndexError
|
225
|
+
)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe '#is_valid_response?' do
|
232
|
+
let(:code) { 200 }
|
233
|
+
context 'when a 200 response code is returned' do
|
234
|
+
it 'should return true' do
|
235
|
+
expect(ExampleIndex2.is_valid_response?(code)).to be true
|
236
|
+
end
|
237
|
+
end
|
238
|
+
context 'when a 201 response code is returned' do
|
239
|
+
let(:code) { 201 }
|
240
|
+
it 'should return true' do
|
241
|
+
expect(ExampleIndex2.is_valid_response?(code)).to be true
|
242
|
+
end
|
243
|
+
end
|
244
|
+
context 'when a 202 response code is returned' do
|
245
|
+
let(:code) { 202 }
|
246
|
+
it 'should return true' do
|
247
|
+
expect(ExampleIndex2.is_valid_response?(code)).to be true
|
248
|
+
end
|
249
|
+
end
|
250
|
+
context 'when a 400 response code is returned' do
|
251
|
+
let(:code) { 400 }
|
252
|
+
it 'should return false' do
|
253
|
+
expect(ExampleIndex2.is_valid_response?(code)).to be false
|
254
|
+
end
|
255
|
+
end
|
256
|
+
context 'when a 401 response code is returned' do
|
257
|
+
let(:code) { 401 }
|
258
|
+
it 'should return false' do
|
259
|
+
expect(ExampleIndex2.is_valid_response?(code)).to be false
|
260
|
+
end
|
261
|
+
end
|
262
|
+
context 'when a 500 response code is returned' do
|
263
|
+
let(:code) { 500 }
|
264
|
+
it 'should return false' do
|
265
|
+
expect(ExampleIndex2.is_valid_response?(code)).to be false
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe '#host' do
|
271
|
+
it 'should return the expected host based on default host & port values' do
|
272
|
+
expect(ExampleIndex2.host).to eq "#{ElasticSearchFramework.host}:#{ElasticSearchFramework.port}"
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
describe '#repository' do
|
277
|
+
it 'should return a ElasticSearchFramework::Repository instance' do
|
278
|
+
expect(ExampleIndex2.repository).to be_a(ElasticSearchFramework::Repository)
|
279
|
+
end
|
280
|
+
it 'should return the same ElasticSearchFramework::Repository instance for multiple calls' do
|
281
|
+
expect(ExampleIndex2.repository).to eq ExampleIndex2.repository
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
describe '#get_item' do
|
286
|
+
let(:id) { 10 }
|
287
|
+
let(:type) { 'default' }
|
288
|
+
it 'should call get on the repository' do
|
289
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:get).with(index: ExampleIndex2, id: id, type: type)
|
290
|
+
ExampleIndex2.get_item(id: id, type: type)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
describe '#put_item' do
|
295
|
+
let(:id) { 10 }
|
296
|
+
let(:type) { 'default' }
|
297
|
+
let(:item) do
|
298
|
+
TestItem.new.tap do |i|
|
299
|
+
i.id = id
|
300
|
+
i.name = 'abc'
|
301
|
+
i.timestamp = Time.now.to_i
|
302
|
+
i.number = 5
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
context 'without specifying op_type' do
|
307
|
+
it 'should call set on the repository with default op_type (index)' do
|
308
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:set).with(entity: item, index: ExampleIndex2, op_type: 'index', type: type)
|
309
|
+
ExampleIndex2.put_item(type: type, item: item)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
context 'with specified op_type' do
|
314
|
+
it 'should call set on the repository with supplied op_type (index)' do
|
315
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:set).with(entity: item, index: ExampleIndex2, op_type: 'index', type: type)
|
316
|
+
ExampleIndex2.put_item(type: type, item: item, op_type: 'index')
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'should call set on the repository with supplied op_type (create)' do
|
320
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:set).with(entity: item, index: ExampleIndex2, op_type: 'create', type: type)
|
321
|
+
ExampleIndex2.put_item(type: type, item: item, op_type: 'create')
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context 'without specifying routing_key' do
|
326
|
+
it 'should call set on the repository without routing_key' do
|
327
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:set).with(entity: item, index: ExampleIndex2, op_type: 'index', type: type)
|
328
|
+
ExampleIndex2.put_item(type: type, item: item, op_type: 'index')
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
context 'with specified routing_key' do
|
333
|
+
it 'should call set on the repository with routing_key' do
|
334
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:set).with(entity: item, index: ExampleIndex2, op_type: 'index', type: type, routing_key: 6)
|
335
|
+
ExampleIndex2.put_item(type: type, item: item, op_type: 'index', routing_key: 6)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
describe '#delete_item' do
|
341
|
+
let(:id) { 10 }
|
342
|
+
let(:type) { 'default' }
|
343
|
+
it 'should call drop on the repository' do
|
344
|
+
expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:drop).with(index: ExampleIndex2, id: id, type: type)
|
345
|
+
ExampleIndex2.delete_item(id: id, type: type)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class ExampleIndex2
|
2
|
+
extend ElasticSearchFramework::ShardedIndex
|
3
|
+
|
4
|
+
index name: 'example_index', version: 2
|
5
|
+
|
6
|
+
mapping name: 'default', field: :name, type: :keyword, index: true
|
7
|
+
end
|
8
|
+
|
9
|
+
class ExampleIndexWithId2
|
10
|
+
extend ElasticSearchFramework::ShardedIndex
|
11
|
+
|
12
|
+
index name: 'example_index', version: 2
|
13
|
+
|
14
|
+
id :number
|
15
|
+
|
16
|
+
mapping name: 'default', field: :name, type: :keyword, index: true
|
17
|
+
end
|
18
|
+
|
19
|
+
class ExampleIndexWithSettings2
|
20
|
+
extend ElasticSearchFramework::ShardedIndex
|
21
|
+
|
22
|
+
index name: 'example_index', version: 2
|
23
|
+
|
24
|
+
normalizer_value = { custom_normalizer: { type: 'custom', char_filter: [], filter: ['lowercase'] } }
|
25
|
+
analyzer_value = { custom_analyzer: { type: 'custom', tokenizer: 'standard', filter: %w(lowercase) } }
|
26
|
+
|
27
|
+
settings name: :number_of_shards, value: 1
|
28
|
+
settings name: :analysis, type: :normalizer, value: normalizer_value
|
29
|
+
settings name: :analysis, type: :analyzer, value: analyzer_value
|
30
|
+
mapping name: 'default', field: :name, type: :keyword, index: true
|
31
|
+
end
|
32
|
+
|
33
|
+
class InvalidExampleIndex2
|
34
|
+
extend ElasticSearchFramework::ShardedIndex
|
35
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class ExampleIndexAlias
|
2
|
+
extend ElasticSearchFramework::IndexAlias
|
3
|
+
|
4
|
+
index ExampleIndex, active: true
|
5
|
+
index ExampleIndex2, active: false
|
6
|
+
|
7
|
+
name :example
|
8
|
+
end
|
9
|
+
|
10
|
+
class InvalidIndexAlias
|
11
|
+
extend ElasticSearchFramework::IndexAlias
|
12
|
+
|
13
|
+
index ExampleIndex, active: false
|
14
|
+
index ExampleIndex2, active: false
|
15
|
+
|
16
|
+
name :example
|
17
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -9,6 +9,9 @@ end
|
|
9
9
|
require 'elastic_search_framework'
|
10
10
|
require_relative '../spec/test_item.rb'
|
11
11
|
require_relative '../spec/example_index'
|
12
|
+
require_relative '../spec/example_index_2'
|
13
|
+
require_relative '../spec/example_index_alias'
|
14
|
+
require_relative '../spec/example_index_alias_2'
|
12
15
|
require 'pry'
|
13
16
|
|
14
17
|
RSpec.configure do |config|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elastic_search_framework
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- vaughanbrittonsage
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -119,14 +119,21 @@ files:
|
|
119
119
|
- lib/elastic_search_framework/exceptions.rb
|
120
120
|
- lib/elastic_search_framework/exceptions/index_error.rb
|
121
121
|
- lib/elastic_search_framework/index.rb
|
122
|
+
- lib/elastic_search_framework/index_alias.rb
|
122
123
|
- lib/elastic_search_framework/logger.rb
|
123
124
|
- lib/elastic_search_framework/query.rb
|
124
125
|
- lib/elastic_search_framework/repository.rb
|
126
|
+
- lib/elastic_search_framework/sharded_index.rb
|
125
127
|
- lib/elastic_search_framework/version.rb
|
128
|
+
- spec/elastic_search_framework/index_alias_spec.rb
|
126
129
|
- spec/elastic_search_framework/index_spec.rb
|
127
130
|
- spec/elastic_search_framework/query_spec.rb
|
128
131
|
- spec/elastic_search_framework/repository_spec.rb
|
132
|
+
- spec/elastic_search_framework/sharded_index_spec.rb
|
129
133
|
- spec/example_index.rb
|
134
|
+
- spec/example_index_2.rb
|
135
|
+
- spec/example_index_alias.rb
|
136
|
+
- spec/example_index_alias_2.rb
|
130
137
|
- spec/spec_helper.rb
|
131
138
|
- spec/test_item.rb
|
132
139
|
homepage: https://github.com/sage/elastic_search_framework
|
@@ -148,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
148
155
|
- !ruby/object:Gem::Version
|
149
156
|
version: '0'
|
150
157
|
requirements: []
|
151
|
-
rubygems_version: 3.
|
158
|
+
rubygems_version: 3.1.2
|
152
159
|
signing_key:
|
153
160
|
specification_version: 4
|
154
161
|
summary: A lightweight framework to for working with elastic search.
|