elastic_search_framework 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3462f6d260c17cc73a60848b61901bd26672a8b7
4
+ data.tar.gz: 9b3ec91367cc9e54d4dea7d6cb2a226c6d3a4486
5
+ SHA512:
6
+ metadata.gz: a367754c71d545eaf6fb439ce9c4c11c5f0dd49dc4c935316c2fa708c3c8464af7125c1f10b6abcf7c5d25babf84a785f54ae142ea3ad907da28453361b79cbf
7
+ data.tar.gz: 845e279c30d93a5542bd23552993bec60ea81912e8ee22ed79f8848a4b5e61a1507f6fed8bcf29982f3232f4e793ac4140b92e1cc3d393b195ca413e8ba2461b
@@ -0,0 +1,38 @@
1
+ require 'hash_kit'
2
+ require 'json'
3
+ require 'curb'
4
+ require_relative 'elastic_search_framework/version'
5
+ require_relative 'elastic_search_framework/logger'
6
+ require_relative 'elastic_search_framework/exceptions'
7
+ require_relative 'elastic_search_framework/repository'
8
+ require_relative 'elastic_search_framework/index'
9
+ require_relative 'elastic_search_framework/query'
10
+
11
+ require 'date'
12
+
13
+ module ElasticSearchFramework
14
+ def self.namespace=(value)
15
+ @namespace = value
16
+ end
17
+ def self.namespace
18
+ @namespace
19
+ end
20
+ def self.namespace_delimiter=(value)
21
+ @namespace_delimiter = value
22
+ end
23
+ def self.namespace_delimiter
24
+ @namespace_delimiter ||= '.'
25
+ end
26
+ def self.host=(value)
27
+ @host = value
28
+ end
29
+ def self.host
30
+ @host
31
+ end
32
+ def self.port=(value)
33
+ @port = value
34
+ end
35
+ def self.port
36
+ @port ||= 9200
37
+ end
38
+ end
@@ -0,0 +1 @@
1
+ require_relative 'exceptions/index_error'
@@ -0,0 +1,6 @@
1
+ module ElasticSearchFramework
2
+ module Exceptions
3
+ class IndexError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,177 @@
1
+ module ElasticSearchFramework
2
+ module Index
3
+ def index(name:, shards: nil)
4
+ unless instance_variable_defined?(:@elastic_search_index_def)
5
+ instance_variable_set(:@elastic_search_index_def, {
6
+ name: "#{name}", shards: shards
7
+ })
8
+ else
9
+ raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Duplicate index description. Name: #{name} | Shards: #{shards}.")
10
+ end
11
+ end
12
+
13
+ def id(field)
14
+ unless instance_variable_defined?(:@elastic_search_index_id)
15
+ instance_variable_set(:@elastic_search_index_id, field)
16
+ else
17
+ raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Duplicate index id. Field: #{field}.")
18
+ end
19
+ end
20
+
21
+ def mapping(name:, field:, type:, analyser:)
22
+
23
+ unless instance_variable_defined?(:@elastic_search_index_mappings)
24
+ instance_variable_set(:@elastic_search_index_mappings, {})
25
+ end
26
+
27
+ mappings = instance_variable_get(:@elastic_search_index_mappings)
28
+
29
+ if mappings[name] == nil
30
+ mappings[name] = {}
31
+ end
32
+
33
+ mappings[name][field] = { type: type, analyser: analyser}
34
+
35
+ instance_variable_set(:@elastic_search_index_mappings, mappings)
36
+
37
+ end
38
+
39
+ def create
40
+
41
+ if !valid?
42
+ raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Invalid Index description specified.")
43
+ end
44
+
45
+ if exists?
46
+ ElasticSearchFramework.logger.debug { "[#{self.class}] - Index already exists."}
47
+ return
48
+ end
49
+
50
+ payload = create_payload(description: description, mappings: mappings)
51
+
52
+ put(payload: payload)
53
+
54
+ end
55
+
56
+ def put(payload:)
57
+ client = curl
58
+ client.url = "#{host}/#{full_name}"
59
+ json = JSON.dump(payload)
60
+ client.http_put(json)
61
+ unless is_valid_response?(client)
62
+ raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self}] - Failed to put index. Payload: #{payload} | Response: #{client.body_str}")
63
+ end
64
+ true
65
+ end
66
+
67
+ def get
68
+ client = curl
69
+ client.url = "#{host}/#{full_name}"
70
+ client.http_get
71
+ result = nil
72
+ if is_valid_response?(client)
73
+ result = JSON.parse(client.body_str)
74
+ end
75
+ result
76
+ end
77
+
78
+ def exists?
79
+ get != nil
80
+ end
81
+
82
+ def delete
83
+ client = curl
84
+ client.url = "#{host}/#{full_name}"
85
+ client.http_delete
86
+ is_valid_response?(client) || client.response_code == 404
87
+ end
88
+
89
+ def create_payload(description:, mappings:)
90
+ payload = {}
91
+
92
+ if description[:shards] != nil
93
+ payload[:settings] = {
94
+ number_of_shards: Integer(description[:shards])
95
+ }
96
+ end
97
+
98
+ if mappings.keys.length > 0
99
+
100
+ payload[:mappings] = {}
101
+
102
+ mappings.keys.each do |name|
103
+ payload[:mappings][name] = {
104
+ properties: {}
105
+ }
106
+ mappings[name].keys.each do |field|
107
+ payload[:mappings][name][:properties][field] = {
108
+ type: mappings[name][field][:type],
109
+ index: mappings[name][field][:analyser]
110
+ }
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ payload
117
+ end
118
+
119
+ def valid?
120
+ self.instance_variable_get(:@elastic_search_index_def) ? true : false
121
+ end
122
+
123
+ def description
124
+ hash = self.instance_variable_get(:@elastic_search_index_def)
125
+ if instance_variable_defined?(:@elastic_search_index_id)
126
+ hash[:id] = self.instance_variable_get(@elastic_search_index_id)
127
+ else
128
+ hash[:id] = :id
129
+ end
130
+ hash
131
+ end
132
+
133
+ def mappings
134
+ self.instance_variable_defined?(:@elastic_search_index_mappings) ? self.instance_variable_get(:@elastic_search_index_mappings) : {}
135
+ end
136
+
137
+ def curl
138
+ Curl::Easy.new
139
+ end
140
+
141
+ def is_valid_response?(client)
142
+ [200,201,202].include?(client.response_code)
143
+ end
144
+
145
+ def full_name
146
+ if ElasticSearchFramework.namespace != nil
147
+ "#{ElasticSearchFramework.namespace}#{ElasticSearchFramework.namespace_delimiter}#{description[:name].downcase}"
148
+ else
149
+ description[:name].downcase
150
+ end
151
+ end
152
+
153
+ def host
154
+ "#{ElasticSearchFramework.host}:#{ElasticSearchFramework.port}"
155
+ end
156
+
157
+ def repository
158
+ ElasticSearchFramework::Repository.new
159
+ end
160
+
161
+ def get_item(id:, type: 'default')
162
+ repository.get(index: self, id: id, type: type)
163
+ end
164
+
165
+ def put_item(type: 'default', item:)
166
+ repository.set(entity: item, index: self, type: type)
167
+ end
168
+
169
+ def delete_item(id:, type: 'default')
170
+ repository.drop(index: self, id: id, type: type)
171
+ end
172
+
173
+ def query
174
+ ElasticSearchFramework::Query.new(index: self)
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,15 @@
1
+ require 'logger'
2
+
3
+ module ElasticSearchFramework
4
+
5
+ def self.logger
6
+ return @@logger
7
+ end
8
+
9
+ def self.set_logger(logger)
10
+ @@logger = logger
11
+ end
12
+
13
+ ElasticSearchFramework.set_logger(Logger.new(STDOUT))
14
+
15
+ end
@@ -0,0 +1,124 @@
1
+ module ElasticSearchFramework
2
+ class Query
3
+
4
+ class InvalidQueryError < StandardError
5
+ def initialize(message)
6
+ super(message)
7
+ end
8
+ end
9
+
10
+ def initialize(index:)
11
+ @index = index
12
+ @parts = []
13
+ end
14
+
15
+ def method_missing(name)
16
+ @parts << { type: :field, value: name }
17
+ self
18
+ end
19
+
20
+ def eq(value)
21
+ condition(expression: ':', value: value)
22
+ self
23
+ end
24
+
25
+ def contains?(value)
26
+ condition(expression: ':', value: value)
27
+ self
28
+ end
29
+
30
+ def not_eq(value)
31
+ field = @parts.last
32
+ unless field[:type] == :field
33
+ raise ::InvalidQueryError.new('The not_eq query part can only be chained to a field.')
34
+ end
35
+ @parts.pop
36
+ @parts << { type: :not_eq, field: field[:value], value: value }
37
+ self
38
+ end
39
+
40
+ def gt(value)
41
+ condition(expression: ':>', value: value)
42
+ self
43
+ end
44
+
45
+ def gt_eq(value)
46
+ condition(expression: ':>=', value: value)
47
+ self
48
+ end
49
+
50
+ def lt(value)
51
+ condition(expression: ':<', value: value)
52
+ self
53
+ end
54
+
55
+ def lt_eq(value)
56
+ condition(expression: ':<=', value: value)
57
+ self
58
+ end
59
+
60
+ def exists?
61
+ field = @parts.last
62
+ unless field[:type] == :field
63
+ raise ::InvalidQueryError.new('The exists? query part can only be chained to a field.')
64
+ end
65
+ @parts.pop
66
+ @parts << { type: :exists?, field: field[:value] }
67
+ self
68
+ end
69
+
70
+ def and
71
+ @parts << { type: :and }
72
+ self
73
+ end
74
+
75
+ def or
76
+ @parts << { type: :or }
77
+ self
78
+ end
79
+
80
+ def execute(limit: 10, count: false)
81
+ query = build
82
+ repository = ElasticSearchFramework::Repository.new
83
+ repository.query(index: @index, expression: query, limit: limit, count: count)
84
+ end
85
+
86
+ def build
87
+ @expression_string = ''
88
+
89
+ @parts.each do |p|
90
+ case p[:type]
91
+ when :field
92
+ @expression_string += ' ' + p[:value].to_s
93
+ when :condition
94
+ @expression_string += p[:expression].to_s + format_value(p[:value])
95
+ when :exists
96
+ @expression_string += ' _exists_:' + p[:field].to_s
97
+ when :not_eq
98
+ @expression_string += ' NOT (' + p[:field].to_s + ':' + format_value(p[:value]) + ')'
99
+ when :and
100
+ @expression_string += ' AND'
101
+ when :or
102
+ @expression_string += ' OR'
103
+ else
104
+ raise 'Invalid query part'
105
+ end
106
+ end
107
+
108
+ return @expression_string.strip
109
+ end
110
+
111
+ def condition(expression:, value:)
112
+ @parts << { type: :condition, expression: expression, value: value }
113
+ end
114
+
115
+ def format_value(value)
116
+ result = value.to_s
117
+ if value.is_a?(String)
118
+ result = '"' + value + '"'
119
+ end
120
+ result
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,79 @@
1
+ module ElasticSearchFramework
2
+ class Repository
3
+
4
+ def set(index:, entity:, type: 'default')
5
+ client = curl
6
+ client.url = "#{host}/#{index.full_name}/#{type.downcase}/#{get_id_value(index: index, entity: entity)}"
7
+ hash = hash_helper.to_hash(entity)
8
+ client.http_put(JSON.dump(hash))
9
+ unless is_valid_response?(client)
10
+ raise ElasticSearchFramework::Exceptions::IndexError.new("An error occurred setting an index document. Response: #{client.body_str}")
11
+ end
12
+ return true
13
+ end
14
+
15
+ def get(index:, id:, type: 'default')
16
+ client = curl
17
+ client.url = "#{host}/#{index.full_name}/#{type.downcase}/#{id}/_source"
18
+ client.get
19
+ if is_valid_response?(client)
20
+ result = JSON.load(client.body_str)
21
+ hash_helper.indifferent!(result)
22
+ return result
23
+ elsif client.response_code == 404
24
+ return nil
25
+ else
26
+ raise ElasticSearchFramework::Exceptions::IndexError.new("An error occurred getting an index document. Response: #{client.body_str}")
27
+ end
28
+ end
29
+
30
+ def drop(index:, id:, type: 'default')
31
+ client = curl
32
+ client.url = "#{host}/#{index.full_name}/#{type.downcase}/#{id}"
33
+ client.delete
34
+ if is_valid_response?(client) || client.response_code == 404
35
+ return true
36
+ else
37
+ raise ElasticSearchFramework::Exceptions::IndexError.new("An error occurred dropping an index document. Response: #{client.body_str}")
38
+ end
39
+ end
40
+
41
+ def query(index:, expression:, type: 'default', limit: 10, count: false)
42
+ client = curl
43
+ client.url = "#{host}/#{index.full_name}/#{type}/_search?q=#{URI.encode(expression)}&size=#{limit}"
44
+ client.get
45
+ if is_valid_response?(client)
46
+ result = JSON.parse(client.body_str)
47
+ hash_helper.indifferent!(result)
48
+ if count
49
+ return result[:hits][:total]
50
+ else
51
+ return result[:hits][:total] > 0 ? result[:hits][:hits] : []
52
+ end
53
+ else
54
+ raise ElasticSearchFramework::Exceptions::IndexError.new("An error occurred executing an index query. Response: #{client.body_str}")
55
+ end
56
+ end
57
+
58
+ def curl
59
+ Curl::Easy.new
60
+ end
61
+
62
+ def is_valid_response?(client)
63
+ [200,201,202].include?(client.response_code)
64
+ end
65
+
66
+ def host
67
+ "#{ElasticSearchFramework.host}:#{ElasticSearchFramework.port}"
68
+ end
69
+
70
+ def hash_helper
71
+ @hash_helper ||= HashKit::Helper.new
72
+ end
73
+
74
+ def get_id_value(index:, entity:)
75
+ entity.instance_variable_get("@#{index.description[:id]}")
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ module ElasticSearchFramework
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,21 @@
1
+ class ExampleIndex
2
+ extend ElasticSearchFramework::Index
3
+
4
+ index name: 'example_index'
5
+
6
+ mapping name: 'default', field: :name, type: :string, analyser: :not_analyzed
7
+
8
+ end
9
+
10
+ class ExampleIndexWithShard
11
+ extend ElasticSearchFramework::Index
12
+
13
+ index name: 'example_index', shards: 1
14
+
15
+ mapping name: 'default', field: :name, type: :string, analyser: :not_analyzed
16
+
17
+ end
18
+
19
+ class InvalidExampleIndex
20
+ extend ElasticSearchFramework::Index
21
+ end
@@ -0,0 +1,206 @@
1
+ RSpec.describe ElasticSearchFramework::Index do
2
+
3
+ describe '#description' do
4
+ it 'should return the index details' do
5
+ expect(ExampleIndex.description).to be_a(Hash)
6
+ expect(ExampleIndex.description[:name]).to eq 'example_index'
7
+ expect(ExampleIndexWithShard.description[:shards]).to eq 1
8
+ end
9
+ end
10
+
11
+ describe '#full_name' do
12
+ let(:namespace) { 'uat' }
13
+ let(:namespace_delimiter) { '.' }
14
+ before do
15
+ ElasticSearchFramework.namespace = namespace
16
+ ElasticSearchFramework.namespace_delimiter = namespace_delimiter
17
+ end
18
+ it 'should return the full index name including namespace and delimiter' do
19
+ expect(ExampleIndex.full_name).to eq "#{ElasticSearchFramework.namespace}#{ElasticSearchFramework.namespace_delimiter}#{ExampleIndex.description[:name]}"
20
+ end
21
+ end
22
+
23
+ describe '#mapping' do
24
+ it 'should add mapping details to the index definition' do
25
+ mappings = ExampleIndex.mappings
26
+ expect(mappings).to be_a(Hash)
27
+ expect(mappings.length).to eq 1
28
+ expect(mappings['default'][:name][:type]).to eq :string
29
+ expect(mappings['default'][:name][:analyser]).to eq :not_analyzed
30
+ end
31
+ end
32
+
33
+ describe '#valid?' do
34
+ context 'for a valid index definition' do
35
+ it 'should return true' do
36
+ expect(ExampleIndex.valid?).to be true
37
+ end
38
+ end
39
+ context 'for an invalid index definition' do
40
+ it 'should return true' do
41
+ expect(InvalidExampleIndex.valid?).to be false
42
+ end
43
+ end
44
+ end
45
+
46
+ describe '#create' do
47
+ before do
48
+ if ExampleIndex.exists?
49
+ ExampleIndex.delete
50
+ end
51
+ end
52
+ it 'should create an index' do
53
+ expect(ExampleIndex.exists?).to be false
54
+ ExampleIndex.create
55
+ expect(ExampleIndex.exists?).to be true
56
+ end
57
+ after do
58
+ ExampleIndex.delete
59
+ end
60
+ end
61
+
62
+ describe '#delete' do
63
+ before do
64
+ unless ExampleIndex.exists?
65
+ ExampleIndex.create
66
+ end
67
+ end
68
+ it 'should create an index' do
69
+ expect(ExampleIndex.exists?).to be true
70
+ ExampleIndex.delete
71
+ expect(ExampleIndex.exists?).to be false
72
+ end
73
+ end
74
+
75
+ describe '#exists?' do
76
+ context 'when an index exists' do
77
+ before do
78
+ ExampleIndex.delete
79
+ ExampleIndex.create
80
+ end
81
+ it 'should return true' do
82
+ expect(ExampleIndex.exists?).to be true
83
+ end
84
+ end
85
+ context 'when an index exists' do
86
+ before do
87
+ ExampleIndex.delete
88
+ end
89
+ it 'should return false' do
90
+ expect(ExampleIndex.exists?).to be false
91
+ end
92
+ end
93
+ end
94
+
95
+ describe '#curl' do
96
+ it 'should return a Curl::Easy instance' do
97
+ expect(ExampleIndex.curl).to be_a(Curl::Easy)
98
+ end
99
+ it 'should return a unique Curl::Easy instance' do
100
+ expect(ExampleIndex.curl).not_to eq ExampleIndex.curl
101
+ end
102
+ end
103
+
104
+ describe '#is_valid_response?' do
105
+ let(:client) { double }
106
+ context 'when a 200 response code is returned' do
107
+ before do
108
+ allow(client).to receive(:response_code).and_return(200)
109
+ end
110
+ it 'should return true' do
111
+ expect(ExampleIndex.is_valid_response?(client)).to be true
112
+ end
113
+ end
114
+ context 'when a 201 response code is returned' do
115
+ before do
116
+ allow(client).to receive(:response_code).and_return(201)
117
+ end
118
+ it 'should return true' do
119
+ expect(ExampleIndex.is_valid_response?(client)).to be true
120
+ end
121
+ end
122
+ context 'when a 202 response code is returned' do
123
+ before do
124
+ allow(client).to receive(:response_code).and_return(202)
125
+ end
126
+ it 'should return true' do
127
+ expect(ExampleIndex.is_valid_response?(client)).to be true
128
+ end
129
+ end
130
+ context 'when a 400 response code is returned' do
131
+ before do
132
+ allow(client).to receive(:response_code).and_return(400)
133
+ end
134
+ it 'should return false' do
135
+ expect(ExampleIndex.is_valid_response?(client)).to be false
136
+ end
137
+ end
138
+ context 'when a 401 response code is returned' do
139
+ before do
140
+ allow(client).to receive(:response_code).and_return(401)
141
+ end
142
+ it 'should return false' do
143
+ expect(ExampleIndex.is_valid_response?(client)).to be false
144
+ end
145
+ end
146
+ context 'when a 500 response code is returned' do
147
+ before do
148
+ allow(client).to receive(:response_code).and_return(500)
149
+ end
150
+ it 'should return false' do
151
+ expect(ExampleIndex.is_valid_response?(client)).to be false
152
+ end
153
+ end
154
+ end
155
+
156
+ describe '#host' do
157
+ it 'should return the expected host based on default host & port values' do
158
+ expect(ExampleIndex.host).to eq "#{ElasticSearchFramework.host}:#{ElasticSearchFramework.port}"
159
+ end
160
+ end
161
+
162
+ describe '#repository' do
163
+ it 'should return a ElasticSearchFramework::Repository instance' do
164
+ expect(ExampleIndex.repository).to be_a(ElasticSearchFramework::Repository)
165
+ end
166
+ it 'should return a unique ElasticSearchFramework::Repository instance' do
167
+ expect(ExampleIndex.repository).not_to eq ExampleIndex.repository
168
+ end
169
+ end
170
+
171
+ describe '#get_item' do
172
+ let(:id) { 10 }
173
+ let(:type) { 'default' }
174
+ it 'should call get on the repository' do
175
+ expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:get).with(index: ExampleIndex, id: id, type: type)
176
+ ExampleIndex.get_item(id: id, type: type)
177
+ end
178
+ end
179
+
180
+ describe '#put_item' do
181
+ let(:id) { 10 }
182
+ let(:type) { 'default' }
183
+ let(:item) do
184
+ TestItem.new.tap do |i|
185
+ i.id = id
186
+ i.name = 'abc'
187
+ i.timestamp = Time.now.to_i
188
+ i.number = 5
189
+ end
190
+ end
191
+ it 'should call set on the repository' do
192
+ expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:set).with(entity: item, index: ExampleIndex, type: type)
193
+ ExampleIndex.put_item(type: type, item: item)
194
+ end
195
+ end
196
+
197
+ describe '#delete_item' do
198
+ let(:id) { 10 }
199
+ let(:type) { 'default' }
200
+ it 'should call drop on the repository' do
201
+ expect_any_instance_of(ElasticSearchFramework::Repository).to receive(:drop).with(index: ExampleIndex, id: id, type: type)
202
+ ExampleIndex.delete_item(id: id, type: type)
203
+ end
204
+ end
205
+
206
+ end
@@ -0,0 +1,82 @@
1
+ RSpec.describe ElasticSearchFramework::Query do
2
+
3
+ subject do
4
+ ElasticSearchFramework::Query.new(index: ExampleIndex)
5
+ end
6
+
7
+ describe '#build' do
8
+ it 'should build the expected query string' do
9
+ subject.name.eq('fred').and.age.gt(18).and.gender.not_eq('male')
10
+ expect(subject.build).to eq 'name:"fred" AND age:>18 AND NOT (gender:"male")'
11
+ end
12
+ end
13
+
14
+ let(:item1) do
15
+ TestItem.new.tap do |i|
16
+ i.id = 1
17
+ i.name = 'fred'
18
+ i.timestamp = Time.now.to_i
19
+ i.number = 5
20
+ end
21
+ end
22
+ let(:item2) do
23
+ TestItem.new.tap do |i|
24
+ i.id = 2
25
+ i.name = 'john'
26
+ i.timestamp = Time.now.to_i - 100
27
+ i.number = 10
28
+ end
29
+ end
30
+ let(:item3) do
31
+ TestItem.new.tap do |i|
32
+ i.id = 3
33
+ i.name = 'mark'
34
+ i.timestamp = Time.now.to_i - 200
35
+ i.number = 15
36
+ end
37
+ end
38
+ let(:item4) do
39
+ TestItem.new.tap do |i|
40
+ i.id = 4
41
+ i.name = 'paul'
42
+ i.timestamp = Time.now.to_i + 300
43
+ i.number = 20
44
+ end
45
+ end
46
+
47
+ describe '#execute' do
48
+ before do
49
+ ExampleIndex.delete
50
+ ExampleIndex.create
51
+
52
+ ExampleIndex.put_item(item: item1)
53
+ ExampleIndex.put_item(item: item2)
54
+ ExampleIndex.put_item(item: item3)
55
+ ExampleIndex.put_item(item: item4)
56
+ sleep 1
57
+ end
58
+
59
+ it 'should return the expected query results' do
60
+ results = ExampleIndex.query.name.eq('fred').execute
61
+ expect(results.length).to eq 1
62
+ expect(results[0][:_source][:name]).to eq item1.name
63
+ expect(results[0][:_source][:id]).to eq item1.id
64
+ expect(results[0][:_source][:timestamp]).to eq item1.timestamp
65
+ expect(results[0][:_source][:number]).to eq item1.number
66
+
67
+ results = ExampleIndex.query.number.gt(5).execute
68
+ expect(results.length).to eq 3
69
+
70
+ results = ExampleIndex.query.number.gt_eq(15).execute
71
+ expect(results.length).to eq 2
72
+
73
+ results = ExampleIndex.query.name.not_eq('john').execute
74
+ expect(results.length).to eq 3
75
+ end
76
+
77
+ after do
78
+ ExampleIndex.delete
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,119 @@
1
+ RSpec.describe ElasticSearchFramework::Repository do
2
+ let(:item1) do
3
+ TestItem.new.tap do |i|
4
+ i.id = 1
5
+ i.name = 'fred'
6
+ i.timestamp = Time.now.to_i
7
+ i.number = 5
8
+ end
9
+ end
10
+ let(:item2) do
11
+ TestItem.new.tap do |i|
12
+ i.id = 2
13
+ i.name = 'john'
14
+ i.timestamp = Time.now.to_i - 100
15
+ i.number = 10
16
+ end
17
+ end
18
+ let(:item3) do
19
+ TestItem.new.tap do |i|
20
+ i.id = 3
21
+ i.name = 'mark'
22
+ i.timestamp = Time.now.to_i - 200
23
+ i.number = 15
24
+ end
25
+ end
26
+ let(:item4) do
27
+ TestItem.new.tap do |i|
28
+ i.id = 4
29
+ i.name = 'paul'
30
+ i.timestamp = Time.now.to_i + 300
31
+ i.number = 20
32
+ end
33
+ end
34
+
35
+ describe '#get' do
36
+ before do
37
+ ExampleIndex.delete
38
+ ExampleIndex.create
39
+ end
40
+ context 'when no index document exists' do
41
+ it 'should return nil' do
42
+ expect(subject.get(index: ExampleIndex, id: item1.id)).to eq nil
43
+ end
44
+ end
45
+ after do
46
+ ExampleIndex.delete
47
+ end
48
+ end
49
+
50
+ describe '#CRUD' do
51
+ before do
52
+ ExampleIndex.delete
53
+ ExampleIndex.create
54
+ end
55
+
56
+ it 'should create, read and delete an index document' do
57
+ subject.set(index: ExampleIndex, entity: item1)
58
+ subject.set(index: ExampleIndex, entity: item2)
59
+ index_item1 = subject.get(index: ExampleIndex, id: item1.id)
60
+ expect(index_item1[:id]).to eq item1.id
61
+ expect(index_item1[:name]).to eq item1.name
62
+ expect(index_item1[:timestamp]).to eq item1.timestamp
63
+ expect(index_item1[:number]).to eq item1.number
64
+ index_item2 = subject.get(index: ExampleIndex, id: item2.id)
65
+ expect(index_item2[:id]).to eq item2.id
66
+ expect(index_item2[:name]).to eq item2.name
67
+ expect(index_item2[:timestamp]).to eq item2.timestamp
68
+ expect(index_item2[:number]).to eq item2.number
69
+ subject.drop(index: ExampleIndex, id: item1.id)
70
+ expect(subject.get(index: ExampleIndex, id: item1.id)).to be_nil
71
+ end
72
+
73
+ after do
74
+ ExampleIndex.delete
75
+ end
76
+ end
77
+
78
+ describe '#query' do
79
+ before do
80
+ ExampleIndex.delete
81
+ ExampleIndex.create
82
+
83
+ subject.set(index: ExampleIndex, entity: item1)
84
+ subject.set(index: ExampleIndex, entity: item2)
85
+ subject.set(index: ExampleIndex, entity: item3)
86
+ subject.set(index: ExampleIndex, entity: item4)
87
+ sleep 1
88
+ end
89
+
90
+ it 'should return the expected query results' do
91
+ results = subject.query(index: ExampleIndex, expression: 'name:"fred"')
92
+ expect(results.length).to eq 1
93
+ expect(results[0][:_source][:name]).to eq item1.name
94
+ expect(results[0][:_source][:id]).to eq item1.id
95
+ expect(results[0][:_source][:timestamp]).to eq item1.timestamp
96
+ expect(results[0][:_source][:number]).to eq item1.number
97
+
98
+ results = subject.query(index: ExampleIndex, expression: 'number:>5')
99
+ expect(results.length).to eq 3
100
+
101
+ results = subject.query(index: ExampleIndex, expression: 'number:>=15')
102
+ expect(results.length).to eq 2
103
+
104
+ results = subject.query(index: ExampleIndex, expression: 'NOT (name:john)')
105
+ expect(results.length).to eq 3
106
+ end
107
+
108
+ after do
109
+ ExampleIndex.delete
110
+ end
111
+ end
112
+
113
+ describe '#host' do
114
+ it 'should return the expected host based on default host & port values' do
115
+ expect(subject.host).to eq "#{ElasticSearchFramework.host}:#{ElasticSearchFramework.port}"
116
+ end
117
+ end
118
+
119
+ end
@@ -0,0 +1,27 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ require 'elastic_search_framework'
4
+ require_relative '../spec/test_item.rb'
5
+ require_relative '../spec/example_index'
6
+ require 'pry'
7
+
8
+ require 'simplecov'
9
+ SimpleCov.start do
10
+ add_filter '/spec/'
11
+ end
12
+
13
+ RSpec.configure do |config|
14
+ config.expect_with :rspec do |expectations|
15
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
16
+ end
17
+
18
+ config.mock_with :rspec do |mocks|
19
+ mocks.verify_partial_doubles = true
20
+ end
21
+
22
+ config.order = :defined
23
+ end
24
+
25
+ ElasticSearchFramework.logger.level = Logger::ERROR
26
+ ElasticSearchFramework.host = 'http://elasticsearch'
27
+ ElasticSearchFramework.port = 9200
data/spec/test_item.rb ADDED
@@ -0,0 +1,6 @@
1
+ class TestItem
2
+ attr_accessor :id
3
+ attr_accessor :name
4
+ attr_accessor :timestamp
5
+ attr_accessor :number
6
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elastic_search_framework
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - vaughanbrittonsage
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-05-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: hash_kit
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.5'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: json
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: curb
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: A lightweight framework to for working with elastic search.
112
+ email:
113
+ - vaughanbritton@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - lib/elastic_search_framework.rb
119
+ - lib/elastic_search_framework/exceptions.rb
120
+ - lib/elastic_search_framework/exceptions/index_error.rb
121
+ - lib/elastic_search_framework/index.rb
122
+ - lib/elastic_search_framework/logger.rb
123
+ - lib/elastic_search_framework/query.rb
124
+ - lib/elastic_search_framework/repository.rb
125
+ - lib/elastic_search_framework/version.rb
126
+ - spec/example_index.rb
127
+ - spec/index_spec.rb
128
+ - spec/query_spec.rb
129
+ - spec/repository_spec.rb
130
+ - spec/spec_helper.rb
131
+ - spec/test_item.rb
132
+ homepage: https://github.com/sage/elastic_search_framework
133
+ licenses:
134
+ - MIT
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.5.1
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: A lightweight framework to for working with elastic search.
156
+ test_files: []