elastictastic 0.5.0 → 0.10.2
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.
- data/LICENSE +1 -1
- data/README.md +161 -10
- data/lib/elastictastic/adapter.rb +84 -0
- data/lib/elastictastic/association.rb +6 -0
- data/lib/elastictastic/basic_document.rb +213 -0
- data/lib/elastictastic/bulk_persistence_strategy.rb +64 -19
- data/lib/elastictastic/callbacks.rb +18 -12
- data/lib/elastictastic/child_collection_proxy.rb +15 -11
- data/lib/elastictastic/client.rb +47 -24
- data/lib/elastictastic/configuration.rb +59 -4
- data/lib/elastictastic/dirty.rb +43 -28
- data/lib/elastictastic/discrete_persistence_strategy.rb +48 -23
- data/lib/elastictastic/document.rb +1 -85
- data/lib/elastictastic/embedded_document.rb +34 -0
- data/lib/elastictastic/errors.rb +17 -5
- data/lib/elastictastic/field.rb +3 -0
- data/lib/elastictastic/mass_assignment_security.rb +2 -4
- data/lib/elastictastic/middleware.rb +66 -84
- data/lib/elastictastic/multi_get.rb +30 -0
- data/lib/elastictastic/multi_search.rb +70 -0
- data/lib/elastictastic/nested_document.rb +3 -27
- data/lib/elastictastic/new_relic_instrumentation.rb +8 -8
- data/lib/elastictastic/observing.rb +8 -6
- data/lib/elastictastic/optimistic_locking.rb +57 -0
- data/lib/elastictastic/parent_child.rb +56 -54
- data/lib/elastictastic/persistence.rb +16 -16
- data/lib/elastictastic/properties.rb +136 -96
- data/lib/elastictastic/railtie.rb +1 -1
- data/lib/elastictastic/rotor.rb +105 -0
- data/lib/elastictastic/scope.rb +186 -56
- data/lib/elastictastic/server_error.rb +20 -1
- data/lib/elastictastic/test_helpers.rb +152 -97
- data/lib/elastictastic/thrift/constants.rb +12 -0
- data/lib/elastictastic/thrift/rest.rb +83 -0
- data/lib/elastictastic/thrift/types.rb +124 -0
- data/lib/elastictastic/thrift_adapter.rb +61 -0
- data/lib/elastictastic/transport_methods.rb +27 -0
- data/lib/elastictastic/validations.rb +11 -13
- data/lib/elastictastic/version.rb +1 -1
- data/lib/elastictastic.rb +148 -27
- data/spec/environment.rb +1 -1
- data/spec/examples/bulk_persistence_strategy_spec.rb +151 -23
- data/spec/examples/callbacks_spec.rb +65 -34
- data/spec/examples/dirty_spec.rb +160 -1
- data/spec/examples/document_spec.rb +168 -106
- data/spec/examples/middleware_spec.rb +1 -61
- data/spec/examples/multi_get_spec.rb +127 -0
- data/spec/examples/multi_search_spec.rb +113 -0
- data/spec/examples/observing_spec.rb +24 -3
- data/spec/examples/optimistic_locking_spec.rb +417 -0
- data/spec/examples/parent_child_spec.rb +73 -33
- data/spec/examples/properties_spec.rb +53 -0
- data/spec/examples/rotor_spec.rb +132 -0
- data/spec/examples/scope_spec.rb +78 -18
- data/spec/examples/search_spec.rb +26 -0
- data/spec/examples/validation_spec.rb +7 -1
- data/spec/models/author.rb +1 -1
- data/spec/models/blog.rb +2 -0
- data/spec/models/comment.rb +1 -1
- data/spec/models/photo.rb +9 -0
- data/spec/models/post.rb +3 -0
- metadata +97 -78
- data/lib/elastictastic/resource.rb +0 -4
- data/spec/examples/active_model_lint_spec.rb +0 -20
@@ -25,68 +25,8 @@ describe Elastictastic::Middleware::LogRequests do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
it 'should log body of POST requests to logger' do
|
28
|
-
|
28
|
+
stub_es_create('default', 'post')
|
29
29
|
client.create('default', 'post', nil, {})
|
30
30
|
io.string.should == "ElasticSearch POST (3ms) /default/post {}\n"
|
31
31
|
end
|
32
32
|
end
|
33
|
-
|
34
|
-
describe Elastictastic::Middleware::Rotor do
|
35
|
-
let(:config) do
|
36
|
-
Elastictastic::Configuration.new.tap do |config|
|
37
|
-
config.hosts = ['http://es1.local', 'http://es2.local']
|
38
|
-
end
|
39
|
-
end
|
40
|
-
let(:client) { Elastictastic::Client.new(config) }
|
41
|
-
let(:last_request) { FakeWeb.last_request }
|
42
|
-
|
43
|
-
it 'should alternate requests between hosts' do
|
44
|
-
expect do
|
45
|
-
2.times do
|
46
|
-
1.upto 2 do |i|
|
47
|
-
host_status(i => true)
|
48
|
-
client.get('default', 'post', '1')
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end.not_to raise_error # We can't check the hostname of last_request in Fakeweb
|
52
|
-
end
|
53
|
-
|
54
|
-
context 'if one host fails' do
|
55
|
-
let!(:now) { Time.now.tap { |now| Time.stub(:now).and_return(now) }}
|
56
|
-
|
57
|
-
before do
|
58
|
-
host_status(1 => false, 2 => true)
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'should try the next host' do
|
62
|
-
client.get('default', 'post', '1').should == { 'success' => true }
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
context 'if all hosts fail' do
|
67
|
-
let!(:now) { Time.now.tap { |now| Time.stub(:now).and_return(now) }}
|
68
|
-
|
69
|
-
before do
|
70
|
-
host_status(1 => false, 2 => false)
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'should raise error if no hosts respond' do
|
74
|
-
expect { client.get('default', 'post', '1') }.to(raise_error Elastictastic::NoServerAvailable)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def host_status(statuses)
|
81
|
-
FakeWeb.clean_registry
|
82
|
-
statuses.each_pair do |i, healthy|
|
83
|
-
url = "http://es#{i}.local/default/post/1"
|
84
|
-
if healthy
|
85
|
-
options = { :body => '{"success":true}' }
|
86
|
-
else
|
87
|
-
options = { :exception => Errno::ECONNREFUSED }
|
88
|
-
end
|
89
|
-
FakeWeb.register_uri(:get, url, options)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Elastictastic::MultiGet do
|
4
|
+
include Elastictastic::TestHelpers
|
5
|
+
|
6
|
+
let(:last_request_body) { Elastictastic.json_decode(last_request.body) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
stub_es_mget(
|
10
|
+
nil,
|
11
|
+
nil,
|
12
|
+
['1', 'post', 'default'], ['2', 'post', 'my_index'], ['3', 'post', 'my_index'],
|
13
|
+
['4', 'post', 'my_index'] => nil
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'with no options' do
|
18
|
+
let!(:posts) do
|
19
|
+
Elastictastic.multi_get do |mget|
|
20
|
+
mget.add(Post, 1)
|
21
|
+
mget.add(Post.in_index('my_index'), 2, 3, 4)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should send request to base path' do
|
26
|
+
last_request.path.should == '/_mget'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should request ids with type and index' do
|
30
|
+
last_request_body.should == {
|
31
|
+
'docs' => [{
|
32
|
+
'_id' => '1',
|
33
|
+
'_type' => 'post',
|
34
|
+
'_index' => 'default'
|
35
|
+
}, {
|
36
|
+
'_id' => '2',
|
37
|
+
'_type' => 'post',
|
38
|
+
'_index' => 'my_index'
|
39
|
+
}, {
|
40
|
+
'_id' => '3',
|
41
|
+
'_type' => 'post',
|
42
|
+
'_index' => 'my_index'
|
43
|
+
}, {
|
44
|
+
'_id' => '4',
|
45
|
+
'_type' => 'post',
|
46
|
+
'_index' => 'my_index'
|
47
|
+
}]
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should return existing docs with IDs' do
|
52
|
+
posts.map(&:id).should == %w(1 2 3)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should set proper indices' do
|
56
|
+
posts.map { |post| post.index.name }.should ==
|
57
|
+
%w(default my_index my_index)
|
58
|
+
end
|
59
|
+
end # context 'with no options'
|
60
|
+
|
61
|
+
context 'with fields specified' do
|
62
|
+
let!(:posts) do
|
63
|
+
Elastictastic.multi_get do |mget|
|
64
|
+
mget.add(Post.fields('title'), '1')
|
65
|
+
mget.add(Post.in_index('my_index').fields('title'), '2', '3')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should inject fields into each identifier' do
|
70
|
+
last_request_body.should == {
|
71
|
+
'docs' => [{
|
72
|
+
'_id' => '1',
|
73
|
+
'_type' => 'post',
|
74
|
+
'_index' => 'default',
|
75
|
+
'fields' => %w(title)
|
76
|
+
}, {
|
77
|
+
'_id' => '2',
|
78
|
+
'_type' => 'post',
|
79
|
+
'_index' => 'my_index',
|
80
|
+
'fields' => %w(title)
|
81
|
+
}, {
|
82
|
+
'_id' => '3',
|
83
|
+
'_type' => 'post',
|
84
|
+
'_index' => 'my_index',
|
85
|
+
'fields' => %w(title)
|
86
|
+
}]
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'with routing specified' do
|
92
|
+
let!(:posts) do
|
93
|
+
Elastictastic.multi_get do |mget|
|
94
|
+
mget.add(Post.routing('foo'), '1')
|
95
|
+
mget.add(Post.in_index('my_index').routing('bar'), '2', '3')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should inject fields into each identifier' do
|
100
|
+
last_request_body.should == {
|
101
|
+
'docs' => [{
|
102
|
+
'_id' => '1',
|
103
|
+
'_type' => 'post',
|
104
|
+
'_index' => 'default',
|
105
|
+
'routing' => 'foo'
|
106
|
+
}, {
|
107
|
+
'_id' => '2',
|
108
|
+
'_type' => 'post',
|
109
|
+
'_index' => 'my_index',
|
110
|
+
'routing' => 'bar'
|
111
|
+
}, {
|
112
|
+
'_id' => '3',
|
113
|
+
'_type' => 'post',
|
114
|
+
'_index' => 'my_index',
|
115
|
+
'routing' => 'bar'
|
116
|
+
}]
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'with no docspecs given' do
|
122
|
+
it 'should gracefully return nothing' do
|
123
|
+
FakeWeb.clean_registry
|
124
|
+
Elastictastic.multi_get {}.to_a.should == []
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Elastictastic::MultiSearch do
|
4
|
+
include Elastictastic::TestHelpers
|
5
|
+
|
6
|
+
let(:request_components) do
|
7
|
+
[].tap do |components|
|
8
|
+
FakeWeb.last_request.body.each_line do |line|
|
9
|
+
components << Elastictastic.json_decode(line) unless line.strip.empty?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '::query' do
|
15
|
+
let(:scopes) do
|
16
|
+
[
|
17
|
+
Post.query(:query_string => { :query => 'pizza' }).size(10),
|
18
|
+
Blog.in_index('my_index').query(:term => { 'name' => 'Pasta' }).size(10),
|
19
|
+
Photo.routing('7').query(:query_string => {:query => 'pizza'}).size(10)
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
before do
|
24
|
+
stub_es_msearch(
|
25
|
+
Array.new(3) { |i| generate_es_hit('post', :source => { 'title' => "post #{i}" }) },
|
26
|
+
Array.new(5) { |i| generate_es_hit('blog', :source => { 'name' => "blog #{i}" }) },
|
27
|
+
Array.new(2) { |i| generate_es_hit('photo', :source => { 'caption' => "photo #{i}" }) }
|
28
|
+
)
|
29
|
+
Elastictastic::MultiSearch.query(scopes)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should send correct type, index, and search_type' do
|
33
|
+
request_components[0].should == { 'index' => 'default', 'type' => 'post', 'search_type' => 'query_then_fetch' }
|
34
|
+
request_components[2].should == { 'index' => 'my_index', 'type' => 'blog', 'search_type' => 'query_then_fetch' }
|
35
|
+
request_components[4].should == { 'index' => 'default', 'type' => 'photo', 'search_type' => 'query_then_fetch', 'routing' => '7' }
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should send correct scope params' do
|
39
|
+
request_components[1].should == scopes[0].params
|
40
|
+
request_components[3].should == scopes[1].params
|
41
|
+
request_components[5].should == scopes[2].params
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should populate scopes with results' do
|
45
|
+
scopes.first.each_with_index { |post, i| post.title.should == "post #{i}" }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '::count' do
|
50
|
+
let(:scopes) do
|
51
|
+
[
|
52
|
+
Post.query(:query_string => { :query => 'pizza' }).size(10),
|
53
|
+
Blog.in_index('my_index').query(:term => { 'name' => 'Pasta' })
|
54
|
+
]
|
55
|
+
end
|
56
|
+
|
57
|
+
before do
|
58
|
+
stub_es_msearch_count(3, 5)
|
59
|
+
Elastictastic::MultiSearch.count(scopes)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should send count search_type' do
|
63
|
+
request_components[0]['search_type'].should == 'count'
|
64
|
+
request_components[2]['search_type'].should == 'count'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should populate counts' do
|
68
|
+
scopes.map { |scope| scope.count }.should == [3, 5]
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should not populate results' do
|
72
|
+
expect { scopes.first.to_a }.
|
73
|
+
to raise_error(FakeWeb::NetConnectNotAllowedError)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'with no components' do
|
78
|
+
it 'should not attempt request' do
|
79
|
+
expect { Elastictastic::MultiSearch.query([]) }.
|
80
|
+
to_not raise_error(FakeWeb::NetConnectNotAllowedError)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'with unbounded scopes' do
|
85
|
+
let(:scopes) do
|
86
|
+
[Post.query(:query_string => { :query => 'pizza' })]
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should throw an exception' do
|
90
|
+
expect { Elastictastic::MultiSearch.query(scopes) }.
|
91
|
+
to raise_error(ArgumentError)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with error in response' do
|
96
|
+
let(:scopes) do
|
97
|
+
[Post.query(:bogus => {}).size(10)]
|
98
|
+
end
|
99
|
+
|
100
|
+
before do
|
101
|
+
stub_request_json(
|
102
|
+
:post,
|
103
|
+
match_es_path('/_msearch'),
|
104
|
+
'responses' => [{ 'error' => 'SearchPhaseExecutionException[Failed to execute phase [query], total failure; shardFailures {[8A2fuICBTdiry42KTfa7uQ][contact_documents-1-development-0][4]: SearchParseException[[contact_documents-1-development-0][4]: from[-1],size[-1]: Parse Failure [Failed to parse source [{\"query\":{\"bogus\": {}}}]]]; nested: QueryParsingException[[contact_documents-1-development-0] No query registered for [bogus]]; }{[8A2fuICBTdiry42KTfa7uQ][contact_documents-1-development-0][3]: SearchParseException[[contact_documents-1-development-0][3]: from[-1],size[-1]: Parse Failure [Failed to parse source [{\"query\":{\"bogus\": {}}}]]]; nested: QueryParsingException[[contact_documents-1-development-0] No query registered for [bogus]]; }]'}]
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should raise error' do
|
109
|
+
expect { Elastictastic::MultiSearch.query(scopes) }.
|
110
|
+
to raise_error(Elastictastic::ServerError::QueryParsingException)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -12,9 +12,9 @@ describe Elastictastic::Observing do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
before do
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
stub_es_create('default', 'post')
|
16
|
+
stub_es_update('default', 'post', id)
|
17
|
+
stub_es_destroy('default', 'post', id)
|
18
18
|
Elastictastic.config.observers = [:post_observer]
|
19
19
|
Elastictastic.config.instantiate_observers
|
20
20
|
end
|
@@ -59,6 +59,13 @@ describe Elastictastic::Observing do
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
+
context 'on create with observing disabled' do
|
63
|
+
it 'should not run any observers' do
|
64
|
+
post.save(:observers => false)
|
65
|
+
post.observers_that_ran.should be_empty
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
62
69
|
context 'on update' do
|
63
70
|
let(:observers) { persisted_post.observers_that_ran }
|
64
71
|
|
@@ -99,6 +106,13 @@ describe Elastictastic::Observing do
|
|
99
106
|
end
|
100
107
|
end
|
101
108
|
|
109
|
+
context 'on update with observers disabled' do
|
110
|
+
it 'should not run any observers' do
|
111
|
+
persisted_post.save(:observers => false)
|
112
|
+
persisted_post.observers_that_ran.should be_empty
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
102
116
|
context 'on destroy' do
|
103
117
|
let(:observers) { persisted_post.observers_that_ran }
|
104
118
|
|
@@ -138,4 +152,11 @@ describe Elastictastic::Observing do
|
|
138
152
|
observers.should include(:after_destroy)
|
139
153
|
end
|
140
154
|
end
|
155
|
+
|
156
|
+
context 'on destroy with observers disabled' do
|
157
|
+
it 'should not run any observers' do
|
158
|
+
persisted_post.destroy(:observers => false)
|
159
|
+
persisted_post.observers_that_ran.should be_empty
|
160
|
+
end
|
161
|
+
end
|
141
162
|
end
|