es-elasticity 0.12.0 → 0.13.3.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +223 -0
- data/.gitignore +10 -7
- data/.rubocop.yml +23 -0
- data/CHANGELOG.md +143 -0
- data/Gemfile +4 -4
- data/Gemfile.lock +94 -0
- data/README.md +17 -3
- data/Rakefile +5 -1
- data/{elasticity.gemspec → es-elasticity.gemspec} +6 -0
- data/lib/elasticity.rb +1 -0
- data/lib/elasticity/index_config.rb +3 -2
- data/lib/elasticity/index_mapper.rb +8 -1
- data/lib/elasticity/instrumented_client.rb +1 -1
- data/lib/elasticity/search.rb +19 -4
- data/lib/elasticity/strategies/alias_index.rb +26 -8
- data/lib/elasticity/strategies/single_index.rb +12 -2
- data/lib/elasticity/version.rb +1 -1
- data/tasks/ci.rake +12 -0
- data/tasks/rdoc.rake +14 -0
- metadata +61 -35
- data/.travis.yml +0 -17
- data/CHANGELOG +0 -76
- data/bin/rake +0 -16
- data/bin/rspec +0 -16
- data/es-elasticity-0.12.0.pre.rc1.gem +0 -0
- data/spec/functional/persistence_spec.rb +0 -439
- data/spec/functional/search_spec.rb +0 -117
- data/spec/functional/segmented_spec.rb +0 -86
- data/spec/rspec_config.rb +0 -42
- data/spec/units/document_spec.rb +0 -63
- data/spec/units/index_config_spec.rb +0 -97
- data/spec/units/index_mapper_spec.rb +0 -4
- data/spec/units/multi_search_response_parser_spec.rb +0 -121
- data/spec/units/multi_search_spec.rb +0 -158
- data/spec/units/search_spec.rb +0 -230
- data/spec/units/strategies/single_index_spec.rb +0 -85
@@ -1,117 +0,0 @@
|
|
1
|
-
RSpec.describe "Search", elasticsearch: true do
|
2
|
-
class CatDoc < Elasticity::Document
|
3
|
-
configure do |c|
|
4
|
-
c.strategy = Elasticity::Strategies::SingleIndex
|
5
|
-
c.document_type = "cat"
|
6
|
-
c.mapping = { "properties" => {
|
7
|
-
name: { type: "text" },
|
8
|
-
description: { type: "text" },
|
9
|
-
age: { type: "integer" }
|
10
|
-
} }
|
11
|
-
end
|
12
|
-
|
13
|
-
attr_accessor :name, :age, :description
|
14
|
-
|
15
|
-
def to_document
|
16
|
-
{ name: name, age: age, description: description }
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
class DogDoc < Elasticity::Document
|
21
|
-
configure do |c|
|
22
|
-
c.strategy = Elasticity::Strategies::SingleIndex
|
23
|
-
c.document_type = "dog"
|
24
|
-
c.mapping = { "properties" => {
|
25
|
-
name: { type: "keyword" },
|
26
|
-
description: { type: "text" },
|
27
|
-
age: { type: "integer" },
|
28
|
-
hungry: { type: "boolean" }
|
29
|
-
} }
|
30
|
-
end
|
31
|
-
attr_accessor :name, :age, :description, :hungry
|
32
|
-
|
33
|
-
def to_document
|
34
|
-
{ name: name, age: age, description: description, hungry: hungry }
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
describe "search_args" do
|
39
|
-
before do
|
40
|
-
CatDoc.recreate_index
|
41
|
-
DogDoc.recreate_index
|
42
|
-
|
43
|
-
@elastic_search_client.cluster.health wait_for_status: 'yellow'
|
44
|
-
|
45
|
-
cat = CatDoc.new(name: "felix the cat", age: 10, description: "I am an old cat")
|
46
|
-
dog = DogDoc.new(name: "fido", age: 4, hungry: true, description: "I am a hungry dog")
|
47
|
-
|
48
|
-
cat.update
|
49
|
-
dog.update
|
50
|
-
|
51
|
-
CatDoc.flush_index
|
52
|
-
end
|
53
|
-
|
54
|
-
describe "explain: true" do
|
55
|
-
def get_explanations(results)
|
56
|
-
results.map(&:_explanation)
|
57
|
-
end
|
58
|
-
|
59
|
-
it "supports on single index search results" do
|
60
|
-
results = CatDoc.search({}, { explain: true }).search_results
|
61
|
-
|
62
|
-
expect(get_explanations(results)).to all( be_truthy )
|
63
|
-
end
|
64
|
-
|
65
|
-
it "supports for multisearch" do
|
66
|
-
cat = CatDoc.search({}, { explain: true })
|
67
|
-
dog = DogDoc.search({})
|
68
|
-
|
69
|
-
subject = Elasticity::MultiSearch.new do |m|
|
70
|
-
m.add(:cats, cat, documents: CatDoc)
|
71
|
-
m.add(:dogs, dog, documents: DogDoc)
|
72
|
-
end
|
73
|
-
|
74
|
-
expect(get_explanations(subject[:cats])).to all( be_truthy )
|
75
|
-
expect(get_explanations(subject[:dogs])).to all( be_nil )
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
describe "highlight" do
|
80
|
-
it "is nil when the highlight does not return" do
|
81
|
-
results = CatDoc.search({}).search_results
|
82
|
-
|
83
|
-
expect(results.first.highlighted_attrs).to be_nil
|
84
|
-
expect(results.first.highlighted).to be_nil
|
85
|
-
end
|
86
|
-
|
87
|
-
describe "when specifying highlight" do
|
88
|
-
let(:cat_search_result) {
|
89
|
-
highlight_search = {
|
90
|
-
query: {
|
91
|
-
multi_match: {
|
92
|
-
query: "cat",
|
93
|
-
fields: ["name^1000", "description"]
|
94
|
-
}
|
95
|
-
},
|
96
|
-
highlight: {
|
97
|
-
fields: {
|
98
|
-
"*": {}
|
99
|
-
}
|
100
|
-
}
|
101
|
-
}
|
102
|
-
|
103
|
-
CatDoc.search(highlight_search).search_results.first
|
104
|
-
}
|
105
|
-
|
106
|
-
it "highlighted_attrs returns the highlighted" do
|
107
|
-
expect(cat_search_result.highlighted_attrs).to eq(["name", "description"])
|
108
|
-
end
|
109
|
-
|
110
|
-
it "highlighted returns a new object with the name transformed" do
|
111
|
-
expect(cat_search_result.highlighted.name.first).to include("felix")
|
112
|
-
expect(cat_search_result.highlighted.description.first).to include("old")
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
@@ -1,86 +0,0 @@
|
|
1
|
-
RSpec.describe "Segmented indexes", elasticsearch: true do
|
2
|
-
subject do
|
3
|
-
Class.new(Elasticity::SegmentedDocument) do
|
4
|
-
def self.name
|
5
|
-
"SomeClass"
|
6
|
-
end
|
7
|
-
|
8
|
-
configure do |c|
|
9
|
-
c.index_base_name = "people"
|
10
|
-
c.document_type = "person"
|
11
|
-
c.mapping = {
|
12
|
-
properties: {
|
13
|
-
name: { type: "text" },
|
14
|
-
},
|
15
|
-
}
|
16
|
-
end
|
17
|
-
|
18
|
-
attr_accessor :name
|
19
|
-
|
20
|
-
def self.by_name(name)
|
21
|
-
search(query: { match: { name: name } })
|
22
|
-
end
|
23
|
-
|
24
|
-
def to_document
|
25
|
-
{ name: name }
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def ensure_index(*segments)
|
31
|
-
@indexed ||= []
|
32
|
-
segments.each(&:recreate_index)
|
33
|
-
@indexed += segments
|
34
|
-
end
|
35
|
-
|
36
|
-
after do
|
37
|
-
Array(@indexed).each { |i| i.delete_index }
|
38
|
-
end
|
39
|
-
|
40
|
-
it "allows all operations on a segment" do
|
41
|
-
seg = subject.segment("A")
|
42
|
-
ensure_index(seg)
|
43
|
-
|
44
|
-
rodrigo = seg.new(name: "rodrigo")
|
45
|
-
|
46
|
-
id, success = rodrigo.update
|
47
|
-
expect(id).to be_kind_of(String)
|
48
|
-
expect(success).to be true
|
49
|
-
|
50
|
-
seg.flush_index
|
51
|
-
results = seg.by_name("rodrigo").to_a.first
|
52
|
-
expect(results.class).to eq rodrigo.class
|
53
|
-
expect(results.name).to eq rodrigo.name
|
54
|
-
|
55
|
-
rodrigo.delete
|
56
|
-
seg.flush_index
|
57
|
-
|
58
|
-
results = seg.by_name("rodrigo").to_a
|
59
|
-
expect(results).to be_empty
|
60
|
-
end
|
61
|
-
|
62
|
-
it "isolates segments from one another" do
|
63
|
-
seg_a = subject.segment("A")
|
64
|
-
seg_b = subject.segment("B")
|
65
|
-
ensure_index(seg_a, seg_b)
|
66
|
-
|
67
|
-
doc_a = seg_a.new(name: "doc a")
|
68
|
-
_, success = doc_a.update
|
69
|
-
expect(success).to be true
|
70
|
-
|
71
|
-
doc_b = seg_b.new(name: "doc b")
|
72
|
-
_, success = doc_b.update
|
73
|
-
expect(success).to be true
|
74
|
-
|
75
|
-
seg_a.flush_index
|
76
|
-
seg_b.flush_index
|
77
|
-
|
78
|
-
res_a = seg_a.by_name("doc").to_a.first
|
79
|
-
expect(res_a.class).to eq doc_a.class
|
80
|
-
expect(res_a.name).to eq doc_a.name
|
81
|
-
|
82
|
-
res_b = seg_b.by_name("doc").to_a.first
|
83
|
-
expect(res_b.class).to eq doc_b.class
|
84
|
-
expect(res_b.name).to eq doc_b.name
|
85
|
-
end
|
86
|
-
end
|
data/spec/rspec_config.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
require "simplecov"
|
2
|
-
require "oj"
|
3
|
-
|
4
|
-
SimpleCov.start
|
5
|
-
|
6
|
-
require "elasticity"
|
7
|
-
require "pry"
|
8
|
-
require "byebug"
|
9
|
-
|
10
|
-
def elastic_search_client
|
11
|
-
return @elastic_search_client if defined?(@elastic_search_client)
|
12
|
-
@elastic_search_client = Elasticsearch::Client.new host: "http://0.0.0.0:9200"
|
13
|
-
end
|
14
|
-
|
15
|
-
logger = Logger.new("spec/spec.log")
|
16
|
-
logger.level = Logger::DEBUG
|
17
|
-
|
18
|
-
ActiveSupport::LogSubscriber.logger = logger
|
19
|
-
Elasticity::LogSubscriber.attach_to(:elasticity)
|
20
|
-
|
21
|
-
RSpec.configure do |c|
|
22
|
-
c.disable_monkey_patching!
|
23
|
-
|
24
|
-
c.before(:suite) do
|
25
|
-
logger.info "init.rspec Starting test suite execution"
|
26
|
-
end
|
27
|
-
|
28
|
-
c.before(:each) do |example|
|
29
|
-
logger.info "spec.rspec #{example.full_description}"
|
30
|
-
|
31
|
-
if example.metadata[:elasticsearch]
|
32
|
-
client = elastic_search_client
|
33
|
-
else
|
34
|
-
client = double(:elasticsearch_client)
|
35
|
-
end
|
36
|
-
|
37
|
-
Elasticity.configure do |e|
|
38
|
-
e.client = client
|
39
|
-
e.namespace = "elasticity_test"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
data/spec/units/document_spec.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
require "elasticity/search"
|
2
|
-
|
3
|
-
RSpec.describe Elasticity::Document do
|
4
|
-
mappings = {
|
5
|
-
properties: {
|
6
|
-
name: { type: "string" },
|
7
|
-
|
8
|
-
items: {
|
9
|
-
type: "nested",
|
10
|
-
properties: {
|
11
|
-
name: { type: "string" },
|
12
|
-
},
|
13
|
-
}
|
14
|
-
}
|
15
|
-
}
|
16
|
-
|
17
|
-
let :klass do
|
18
|
-
Class.new(described_class) do
|
19
|
-
def self.name
|
20
|
-
"SomeClass"
|
21
|
-
end
|
22
|
-
|
23
|
-
configure do |c|
|
24
|
-
c.index_base_name = "class_names"
|
25
|
-
c.document_type = "class_name"
|
26
|
-
c.mapping = mappings
|
27
|
-
c.settings = { number_of_shards: 2 }
|
28
|
-
end
|
29
|
-
|
30
|
-
attr_accessor :name, :items
|
31
|
-
|
32
|
-
def to_document
|
33
|
-
{ name: name, items: items }
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
let :strategy do
|
39
|
-
double(:strategy)
|
40
|
-
end
|
41
|
-
|
42
|
-
before :each do
|
43
|
-
allow(Elasticity::Strategies::AliasIndex).to receive(:new).and_return(strategy)
|
44
|
-
end
|
45
|
-
|
46
|
-
it "requires subclasses to define to_document method" do
|
47
|
-
expect { Class.new(described_class).new.to_document }.to raise_error(NotImplementedError)
|
48
|
-
end
|
49
|
-
|
50
|
-
context "instance" do
|
51
|
-
subject { klass.new _id: 1, name: "Foo", items: [{ name: "Item1" }] }
|
52
|
-
|
53
|
-
it "stores the document in the strategy" do
|
54
|
-
expect(strategy).to receive(:index_document).with("class_name", 1, { name: "Foo", items: [{ name: "Item1" }] }).and_return("_id" => "1", "created" => true)
|
55
|
-
subject.update
|
56
|
-
end
|
57
|
-
|
58
|
-
it "assigns number_of_shards if present" do
|
59
|
-
expect(subject.config.settings[:number_of_shards]).to eq 2
|
60
|
-
expect(subject.config.definition[:settings][:number_of_shards]).to eq 2
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,97 +0,0 @@
|
|
1
|
-
require 'elasticity/index_config'
|
2
|
-
|
3
|
-
RSpec.describe Elasticity::IndexConfig do
|
4
|
-
let(:elasticity_config) { double("config", client: double("client", versions: ["5.3.4"])) }
|
5
|
-
subject { }
|
6
|
-
|
7
|
-
let(:defaults) do
|
8
|
-
{
|
9
|
-
index_base_name: 'users',
|
10
|
-
document_type: 'user'
|
11
|
-
}
|
12
|
-
end
|
13
|
-
|
14
|
-
it 'accepts default configuration options' do
|
15
|
-
config = described_class.new(elasticity_config, defaults) {}
|
16
|
-
expect(config.index_base_name).to eql('users')
|
17
|
-
expect(config.document_type).to eql('user')
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'overrides defaults' do
|
21
|
-
config = described_class.new(elasticity_config, defaults) do |c|
|
22
|
-
c.index_base_name = 'user_documents'
|
23
|
-
c.document_type = 'users'
|
24
|
-
end
|
25
|
-
|
26
|
-
expect(config.index_base_name).to eql('user_documents')
|
27
|
-
expect(config.document_type).to eql('users')
|
28
|
-
end
|
29
|
-
|
30
|
-
context "subclass warnings and exceptions" do
|
31
|
-
class Multied < Elasticity::Document
|
32
|
-
end
|
33
|
-
describe "multi_mapping exceptions" do
|
34
|
-
it "raises an exception for version 7 and above if subclasses are configured" do
|
35
|
-
stub_version("7.0.0")
|
36
|
-
expect do
|
37
|
-
Multied.configure do |c|
|
38
|
-
c.index_base_name = "cats_and_dogs"
|
39
|
-
c.strategy = Elasticity::Strategies::SingleIndex
|
40
|
-
c.subclasses = { cat: "Cat", dog: "Dog" }
|
41
|
-
end
|
42
|
-
end.to raise_error(
|
43
|
-
Elasticity::IndexConfig::SubclassError,
|
44
|
-
Elasticity::IndexConfig::SUBCLASSES_ERROR
|
45
|
-
)
|
46
|
-
end
|
47
|
-
|
48
|
-
it "does not raise an exception for version 7 and above if no subclasses are configured" do
|
49
|
-
stub_version("7.0.0")
|
50
|
-
expect do
|
51
|
-
Multied.configure do |c|
|
52
|
-
c.index_base_name = "cats_and_dogs"
|
53
|
-
c.strategy = Elasticity::Strategies::SingleIndex
|
54
|
-
end
|
55
|
-
end.to_not raise_error
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
describe "multi_mapping warnings" do
|
60
|
-
it "warns if multi_mapping is not supported by the ES version" do
|
61
|
-
stub_version("6.0.2")
|
62
|
-
expect do
|
63
|
-
Multied.configure do |c|
|
64
|
-
c.index_base_name = "cats_and_dogs"
|
65
|
-
c.strategy = Elasticity::Strategies::SingleIndex
|
66
|
-
c.subclasses = { cat: "Cat", dog: "Dog" }
|
67
|
-
end
|
68
|
-
end.to output("#{Elasticity::IndexConfig::SUBCLASSES_WARNING}\n").to_stderr
|
69
|
-
end
|
70
|
-
|
71
|
-
it "does not warn if multi_mapping is supported by the ES version" do
|
72
|
-
stub_version("5.3.1")
|
73
|
-
expect do
|
74
|
-
Multied.configure do |c|
|
75
|
-
c.index_base_name = "cats_and_dogs"
|
76
|
-
c.strategy = Elasticity::Strategies::SingleIndex
|
77
|
-
c.subclasses = { cat: "Cat", dog: "Dog" }
|
78
|
-
end
|
79
|
-
end.to_not output.to_stderr
|
80
|
-
end
|
81
|
-
|
82
|
-
it "does not warn when no subclasses are configured" do
|
83
|
-
stub_version("6.0.2")
|
84
|
-
expect do
|
85
|
-
Multied.configure do |c|
|
86
|
-
c.index_base_name = "cats_and_dogs"
|
87
|
-
c.strategy = Elasticity::Strategies::SingleIndex
|
88
|
-
end
|
89
|
-
end.to_not output.to_stderr
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def stub_version(version)
|
94
|
-
allow_any_instance_of(Elasticity::InstrumentedClient).to receive(:versions).and_return([version])
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
@@ -1,121 +0,0 @@
|
|
1
|
-
require "elasticity/multi_search_response_parser"
|
2
|
-
|
3
|
-
RSpec.describe Elasticity::MultiSearchResponseParser do
|
4
|
-
describe ".parse" do
|
5
|
-
let :response do
|
6
|
-
{
|
7
|
-
"hits" => {
|
8
|
-
"total" => 2,
|
9
|
-
"hits" => [
|
10
|
-
{ "_id" => 1, "_source" => { "name" => "foo" }},
|
11
|
-
{ "_id" => 2, "_source" => { "name" => "bar" }}
|
12
|
-
]
|
13
|
-
}
|
14
|
-
}
|
15
|
-
end
|
16
|
-
|
17
|
-
let :klass do
|
18
|
-
Class.new do
|
19
|
-
include ActiveModel::Model
|
20
|
-
attr_accessor :_id, :name
|
21
|
-
|
22
|
-
def self.map_hit(hit)
|
23
|
-
new(_id: hit["_id"], name: hit["_source"]["name"])
|
24
|
-
end
|
25
|
-
|
26
|
-
def ==(other)
|
27
|
-
self._id == other._id && self.name == other.name
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
let :search do
|
33
|
-
body = {
|
34
|
-
index: "index_first",
|
35
|
-
type: "document_first",
|
36
|
-
search: { search: :first, size: 2 }
|
37
|
-
}
|
38
|
-
|
39
|
-
{
|
40
|
-
search_definition: OpenStruct.new(body: body),
|
41
|
-
documents: klass
|
42
|
-
}
|
43
|
-
end
|
44
|
-
|
45
|
-
it "parses a simple reponse" do
|
46
|
-
search_result = described_class.parse(response, search)
|
47
|
-
|
48
|
-
expect(search_result[0].name).to eq "foo"
|
49
|
-
expect(search_result[1].name).to eq "bar"
|
50
|
-
end
|
51
|
-
|
52
|
-
context "for a 400 error response" do
|
53
|
-
let(:response) do
|
54
|
-
{
|
55
|
-
"error" => {
|
56
|
-
"root_cause" => [
|
57
|
-
{
|
58
|
-
"type" => "too_many_clauses",
|
59
|
-
"reason" => "too_many_clauses: maxClauseCount is set to 1024"
|
60
|
-
}
|
61
|
-
],
|
62
|
-
},
|
63
|
-
"status" => 400
|
64
|
-
}
|
65
|
-
end
|
66
|
-
|
67
|
-
it "raises an error for the given status code" do
|
68
|
-
expect { described_class.parse response, search }.to(
|
69
|
-
raise_error Elasticsearch::Transport::Transport::Errors::BadRequest,
|
70
|
-
response.to_json
|
71
|
-
)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
context "for a 500 error response" do
|
76
|
-
let(:response) do
|
77
|
-
{
|
78
|
-
"error" => {
|
79
|
-
"root_cause" => [
|
80
|
-
{
|
81
|
-
"type" => "not_index_found",
|
82
|
-
"reason" => "not_index_found: index bla was not found"
|
83
|
-
}
|
84
|
-
],
|
85
|
-
},
|
86
|
-
"status" => 500
|
87
|
-
}
|
88
|
-
end
|
89
|
-
|
90
|
-
it "raises an error for the given status code" do
|
91
|
-
expect { described_class.parse response, search }.to(
|
92
|
-
raise_error Elasticsearch::Transport::Transport::Errors::InternalServerError,
|
93
|
-
response.to_json
|
94
|
-
)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
context "for an unknown error response" do
|
99
|
-
let(:response) do
|
100
|
-
{
|
101
|
-
"error" => {
|
102
|
-
"root_cause" => [
|
103
|
-
{
|
104
|
-
"type" => "known_error",
|
105
|
-
"reason" => "known_error: Something wrong happened"
|
106
|
-
}
|
107
|
-
],
|
108
|
-
},
|
109
|
-
"status" => 555
|
110
|
-
}
|
111
|
-
end
|
112
|
-
|
113
|
-
it "raises an unkown error for an known status code" do
|
114
|
-
expect { described_class.parse response, search }.to(
|
115
|
-
raise_error Elasticity::MultiSearchResponseParser::UnknownError,
|
116
|
-
response.to_json
|
117
|
-
)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|