elastic_search_framework 2.3.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 660bab65bf91d16e1788d6f43c47e979ab70d6367b6d964c5db5edb2fd94405e
4
- data.tar.gz: 6132de6d0f951d36382e1d5023eb1b22240b66df004192f291434dae80284ff4
3
+ metadata.gz: 843e0d64a790e34d0cca88a47eb6824530b366a3f2f5732d09e8e7bae8437151
4
+ data.tar.gz: 914a7d0b4730df74b4cd8851d019f9cca83335951ba808b3b2a942d3398a0d8a
5
5
  SHA512:
6
- metadata.gz: 35187a0efc4484e04f90db902db59fe7f599e83986ff16621c8b554bcc35c0d54167fc8663e11bb53d8048088707e31bf3fc191f5dcb970eaa244ebe7daeed7b
7
- data.tar.gz: 82da0e555366738552a4bb4c522e0c6a4d31d83e82e3f4774851d2fc0ac72f291bcf351aa0e2012e31cfaf3058be8b9b1d1cacf8750ec85da5b84db72b89e5f9
6
+ metadata.gz: 69b9c1c1e39841ad54cc1daf0b9895664ca4af635786dfc703c4ac8aa313af6146be873d26ca6358143f2bb1be11c453a28913a10437202524f8dbc4a75d93d9
7
+ data.tar.gz: e2c9a9d2ad91996c24837e2c7982b1e5a5f4a9a2d01c191e22a268f90fb9e8cbb63c2560b2cb5c105c9ac3c17dd930041bdda605512e389815e43747146f5677
@@ -9,6 +9,7 @@ require_relative 'elastic_search_framework/exceptions'
9
9
  require_relative 'elastic_search_framework/repository'
10
10
  require_relative 'elastic_search_framework/index'
11
11
  require_relative 'elastic_search_framework/index_alias'
12
+ require_relative 'elastic_search_framework/sharded_index'
12
13
  require_relative 'elastic_search_framework/query'
13
14
 
14
15
  module ElasticSearchFramework
@@ -15,6 +15,10 @@ module ElasticSearchFramework
15
15
  instance_variable_defined?(:@elastic_search_index_version) ? instance_variable_get(:@elastic_search_index_version) : 0
16
16
  end
17
17
 
18
+ def routing_enabled?
19
+ false
20
+ end
21
+
18
22
  def id(field)
19
23
  unless instance_variable_defined?(:@elastic_search_index_id)
20
24
  instance_variable_set(:@elastic_search_index_id, field)
@@ -168,16 +172,25 @@ module ElasticSearchFramework
168
172
  @repository ||= ElasticSearchFramework::Repository.new
169
173
  end
170
174
 
171
- def get_item(id:, type: 'default')
172
- repository.get(index: self, id: id, type: type)
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)
173
180
  end
174
181
 
175
- def put_item(type: 'default', item:, op_type: 'index')
176
- repository.set(entity: item, index: self, type: type, op_type: op_type)
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)
177
187
  end
178
188
 
179
- def delete_item(id:, type: 'default')
180
- repository.drop(index: self, id: id, type: type)
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)
181
194
  end
182
195
 
183
196
  def query
@@ -123,24 +123,39 @@ module ElasticSearchFramework
123
123
  @repository ||= ElasticSearchFramework::Repository.new
124
124
  end
125
125
 
126
- def get_item(id:, type: "default")
127
- repository.get(index: self, id: id, type: type)
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)
128
133
  end
129
134
 
130
- def put_item(type: "default", item:, op_type: 'index')
135
+ def put_item(type: "default", item:, op_type: 'index', routing_key: nil)
131
136
  indexes.each do |index|
132
- repository.set(entity: item, index: index[:klass], type: type, op_type: op_type)
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)
133
141
  end
134
142
  end
135
143
 
136
- def delete_item(id:, type: "default")
144
+ def delete_item(id:, type: "default", routing_key: nil)
137
145
  indexes.each do |index|
138
- repository.drop(index: index[:klass], id: id, type: type)
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)
139
150
  end
140
151
  end
141
152
 
142
153
  def query
143
154
  ElasticSearchFramework::Query.new(index: self)
144
155
  end
156
+
157
+ def routing_enabled?
158
+ indexes.find(active: true).first[:klass].routing_enabled?
159
+ end
145
160
  end
146
161
  end
@@ -1,8 +1,11 @@
1
1
  module ElasticSearchFramework
2
2
  class Repository
3
3
 
4
- def set(index:, entity:, type: 'default', op_type: 'index')
5
- uri = URI("#{host}/#{index.full_name}/#{type.downcase}/#{get_id_value(index: index, entity: entity)}?op_type=#{op_type}")
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)
@@ -24,8 +27,11 @@ module ElasticSearchFramework
24
27
  end
25
28
  end
26
29
 
27
- def get(index:, id:, type: 'default')
28
- uri = URI("#{host}/#{index.full_name}/#{type.downcase}/#{id}/_source")
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)
29
35
 
30
36
  request = Net::HTTP::Get.new(uri.request_uri)
31
37
 
@@ -46,8 +52,11 @@ module ElasticSearchFramework
46
52
  end
47
53
  end
48
54
 
49
- def drop(index:, id:, type: 'default')
50
- uri = URI("#{host}/#{index.full_name}/#{type.downcase}/#{id}")
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)
51
60
 
52
61
  request = Net::HTTP::Delete.new(uri.request_uri)
53
62
 
@@ -64,8 +73,11 @@ module ElasticSearchFramework
64
73
  end
65
74
  end
66
75
 
67
- def query(index:, expression:, type: 'default', limit: 10, count: false)
68
- uri = URI("#{host}/#{index.full_name}/#{type}/_search?q=#{URI.encode(expression)}&size=#{limit}")
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)
69
81
 
70
82
  request = Net::HTTP::Get.new(uri.request_uri)
71
83
 
@@ -86,8 +98,11 @@ module ElasticSearchFramework
86
98
  end
87
99
  end
88
100
 
89
- def json_query(index_name:, json_query:, type: 'default')
90
- uri = URI("#{host}/#{index_name}/#{type}/_search")
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)
91
106
 
92
107
  request = Net::HTTP::Get.new(uri.request_uri)
93
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
@@ -1,3 +1,3 @@
1
1
  module ElasticSearchFramework
2
- VERSION = '2.3.1'
2
+ VERSION = '2.4.0'
3
3
  end
@@ -108,9 +108,23 @@ RSpec.describe ElasticSearchFramework::Index do
108
108
  describe '#get_item' do
109
109
  let(:id) { 10 }
110
110
  let(:type) { 'default' }
111
- it 'should call get on the repository' do
112
- expect(ExampleIndexAlias.repository).to receive(:get).with(index: ExampleIndexAlias, id: id, type: type).once
113
- ExampleIndexAlias.get_item(id: id, type: type)
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
114
128
  end
115
129
  end
116
130
 
@@ -128,22 +142,22 @@ RSpec.describe ElasticSearchFramework::Index do
128
142
  context 'without specifying op_type' do
129
143
  it 'should call set on the repository for each index of the alias with default op_type (index)' do
130
144
  expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex, type: type, op_type: 'index').once
131
- expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex2, type: type, op_type: 'index').once
132
- ExampleIndexAlias.put_item(type: type, item: item)
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)
133
147
  end
134
148
  end
135
149
 
136
150
  context 'with specified op_type' do
137
151
  it 'should call set on the repository for each index of the alias with supplied op_type (index)' do
138
152
  expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex, type: type, op_type: 'index').once
139
- expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex2, type: type, op_type: 'index').once
140
- ExampleIndexAlias.put_item(type: type, item: item, op_type: 'index')
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)
141
155
  end
142
156
 
143
157
  it 'should call set on the repository for each index of the alias with supplied op_type (create)' do
144
158
  expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex, type: type, op_type: 'create').once
145
- expect(ExampleIndexAlias.repository).to receive(:set).with(entity: item, index: ExampleIndex2, type: type, op_type: 'create').once
146
- ExampleIndexAlias.put_item(type: type, item: item, op_type: 'create')
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)
147
161
  end
148
162
  end
149
163
  end
@@ -153,8 +167,8 @@ RSpec.describe ElasticSearchFramework::Index do
153
167
  let(:type) { 'default' }
154
168
  it 'should call drop on the repository for each index of the alias' do
155
169
  expect(ExampleIndexAlias.repository).to receive(:drop).with(index: ExampleIndex, id: id, type: type).once
156
- expect(ExampleIndexAlias.repository).to receive(:drop).with(index: ExampleIndex2, id: id, type: type).once
157
- ExampleIndexAlias.delete_item(id: id, type: type)
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)
158
172
  end
159
173
  end
160
174
  end
@@ -79,6 +79,7 @@ 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
  }
@@ -320,6 +321,20 @@ RSpec.describe ElasticSearchFramework::Index do
320
321
  ExampleIndex.put_item(type: type, item: item, op_type: 'create')
321
322
  end
322
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
337
+ end
323
338
  end
324
339
 
325
340
  describe '#delete_item' 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
@@ -1,5 +1,5 @@
1
1
  class ExampleIndex2
2
- extend ElasticSearchFramework::Index
2
+ extend ElasticSearchFramework::ShardedIndex
3
3
 
4
4
  index name: 'example_index', version: 2
5
5
 
@@ -7,7 +7,7 @@ class ExampleIndex2
7
7
  end
8
8
 
9
9
  class ExampleIndexWithId2
10
- extend ElasticSearchFramework::Index
10
+ extend ElasticSearchFramework::ShardedIndex
11
11
 
12
12
  index name: 'example_index', version: 2
13
13
 
@@ -17,7 +17,7 @@ class ExampleIndexWithId2
17
17
  end
18
18
 
19
19
  class ExampleIndexWithSettings2
20
- extend ElasticSearchFramework::Index
20
+ extend ElasticSearchFramework::ShardedIndex
21
21
 
22
22
  index name: 'example_index', version: 2
23
23
 
@@ -29,3 +29,7 @@ class ExampleIndexWithSettings2
29
29
  settings name: :analysis, type: :analyzer, value: analyzer_value
30
30
  mapping name: 'default', field: :name, type: :keyword, index: true
31
31
  end
32
+
33
+ class InvalidExampleIndex2
34
+ extend ElasticSearchFramework::ShardedIndex
35
+ end
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.3.1
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: 2021-05-06 00:00:00.000000000 Z
11
+ date: 2021-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -123,11 +123,13 @@ files:
123
123
  - lib/elastic_search_framework/logger.rb
124
124
  - lib/elastic_search_framework/query.rb
125
125
  - lib/elastic_search_framework/repository.rb
126
+ - lib/elastic_search_framework/sharded_index.rb
126
127
  - lib/elastic_search_framework/version.rb
127
128
  - spec/elastic_search_framework/index_alias_spec.rb
128
129
  - spec/elastic_search_framework/index_spec.rb
129
130
  - spec/elastic_search_framework/query_spec.rb
130
131
  - spec/elastic_search_framework/repository_spec.rb
132
+ - spec/elastic_search_framework/sharded_index_spec.rb
131
133
  - spec/example_index.rb
132
134
  - spec/example_index_2.rb
133
135
  - spec/example_index_alias.rb
@@ -153,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
155
  - !ruby/object:Gem::Version
154
156
  version: '0'
155
157
  requirements: []
156
- rubygems_version: 3.0.8
158
+ rubygems_version: 3.1.2
157
159
  signing_key:
158
160
  specification_version: 4
159
161
  summary: A lightweight framework to for working with elastic search.