elastic_search_framework 2.3.1 → 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 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.