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 +7 -0
- data/lib/elastic_search_framework.rb +38 -0
- data/lib/elastic_search_framework/exceptions.rb +1 -0
- data/lib/elastic_search_framework/exceptions/index_error.rb +6 -0
- data/lib/elastic_search_framework/index.rb +177 -0
- data/lib/elastic_search_framework/logger.rb +15 -0
- data/lib/elastic_search_framework/query.rb +124 -0
- data/lib/elastic_search_framework/repository.rb +79 -0
- data/lib/elastic_search_framework/version.rb +3 -0
- data/spec/example_index.rb +21 -0
- data/spec/index_spec.rb +206 -0
- data/spec/query_spec.rb +82 -0
- data/spec/repository_spec.rb +119 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/test_item.rb +6 -0
- metadata +156 -0
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,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,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,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
|
data/spec/index_spec.rb
ADDED
@@ -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
|
data/spec/query_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
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: []
|