waistband 0.8.5 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ module Waistband
2
+ class Result
3
+
4
+ def initialize(result_hash)
5
+ @result_hash = result_hash
6
+ end
7
+
8
+ def method_missing(method_name, *args, &block)
9
+ return @result_hash[method_name.to_s] if @result_hash.keys.include?(method_name.to_s)
10
+ return @result_hash['_source'][method_name.to_s] if @result_hash['_source'] && @result_hash['_source'].keys.include?(method_name.to_s)
11
+ nil
12
+ end
13
+
14
+ def respond_to_missing?(method_name, include_private = false)
15
+ return true if @result_hash.keys.include?(method_name.to_s)
16
+ return true if @result_hash['_source'] && @result_hash['_source'].keys.include?(method_name.to_s)
17
+ super
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,53 @@
1
+ require 'waistband/result'
2
+
3
+ module Waistband
4
+ class SearchResults
5
+
6
+ DEFAULT_PAGE_SIZE = 20
7
+
8
+ def initialize(search_hash, options = {})
9
+ @page = options[:page] || 1
10
+ @page_size = options[:page_size] || DEFAULT_PAGE_SIZE
11
+ @search_hash = search_hash
12
+ end
13
+
14
+ def hits
15
+ raise ::Waistband::Errors::NoSearchHits.new("No search hits!") unless @search_hash['hits']
16
+ @search_hash['hits']['hits']
17
+ end
18
+
19
+ def results
20
+ raise ::Waistband::Errors::NoSearchHits.new("No search hits!") unless @search_hash['hits']
21
+
22
+ hits.map do |hit|
23
+ ::Waistband::Result.new(hit)
24
+ end
25
+ end
26
+
27
+ def paginated_hits
28
+ raise "Kaminari gem not found for pagination" unless defined?(Kaminari)
29
+ Kaminari.paginate_array(hits, total_count: total_results).page(@page).per(@page_size)
30
+ end
31
+
32
+ def paginated_results
33
+ raise "Kaminari gem not found for pagination" unless defined?(Kaminari)
34
+ Kaminari.paginate_array(results, total_count: total_results).page(@page).per(@page_size)
35
+ end
36
+
37
+ def total_results
38
+ raise ::Waistband::Errors::NoSearchHits.new("No search hits!") unless @search_hash['hits']
39
+ @search_hash['hits']['total']
40
+ end
41
+
42
+ def method_missing(method_name, *args, &block)
43
+ return @search_hash[method_name.to_s] if @search_hash.keys.include?(method_name.to_s)
44
+ super
45
+ end
46
+
47
+ def respond_to_missing?(method_name, include_private = false)
48
+ return true if @search_hash.keys.include?(method_name.to_s)
49
+ super
50
+ end
51
+
52
+ end
53
+ end
@@ -1,3 +1,3 @@
1
1
  module Waistband
2
- VERSION = "0.8.5"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -1,12 +1,16 @@
1
1
  development: &DEV
2
2
  timeout: 2
3
+ retries: 5
4
+ reload_on_failure: true
3
5
  servers:
4
6
  server1:
5
- host: http://localhost
7
+ host: localhost
6
8
  port: 9200
9
+ protocol: http
7
10
  server2:
8
- host: http://127.0.0.1
11
+ host: 127.0.0.1
9
12
  port: 9200
13
+ protocol: http
10
14
 
11
15
  test:
12
16
  <<: *DEV
@@ -5,40 +5,37 @@ describe Waistband::Configuration do
5
5
  let(:config) { Waistband.config }
6
6
 
7
7
  it "loads config yml" do
8
- config.host.should match /http\:\/\//
9
- config.port.should eql 9200
10
- config.timeout.should eql 2
8
+ expect(config.timeout).to eql 2
11
9
  end
12
10
 
13
11
  it "loads indexes config" do
14
- config.index('search').should be_a Hash
15
- config.index('search')['settings']['index']['number_of_shards'].should eql 1
12
+ expect(config.index('search')).to be_a Hash
13
+ expect(config.index('search')['settings']['index']['number_of_shards']).to eql 1
16
14
  end
17
15
 
18
16
  it "loads multiple indexes config" do
19
- config.index('events').should be_a Hash
20
- config.index('events')['settings']['index']['number_of_shards'].should eql 1
17
+ expect(config.index('events')).to be_a Hash
18
+ expect(config.index('events')['settings']['index']['number_of_shards']).to eql 1
21
19
  end
22
20
 
23
- describe '#servers' do
21
+ it "proxies the client" do
22
+ expect(::Waistband.config.client).to be_a ::Elasticsearch::Transport::Client
23
+ expect(::Waistband.client).to be_a ::Elasticsearch::Transport::Client
24
+ end
24
25
 
25
- it "returns array of all available servers' configs" do
26
- config.servers.should be_an Array
27
- config.servers.size.should eql 2
26
+ describe '#hosts' do
28
27
 
29
- config.servers.each_with_index do |server, i|
30
- server['host'].should match /http\:\/\//
31
- server['port'].should eql 9200
28
+ it "returns array of all available servers' configs" do
29
+ expect(config.hosts).to be_an Array
30
+ expect(config.hosts.size).to eql 2
32
31
 
33
- server['_id'].should be_present
34
- server['_id'].length.should eql 40
32
+ config.hosts.each_with_index do |server, i|
33
+ expect(server['host']).to match /127\.0\.0\.1|localhost/
34
+ expect(server['port']).to eql 9200
35
+ expect(server['protocol']).to eql 'http'
35
36
  end
36
37
  end
37
38
 
38
- it "servers ids should be unique" do
39
- config.servers[0]['_id'].should_not eql config.servers[1]['_id']
40
- end
41
-
42
39
  end
43
40
 
44
41
  end
@@ -7,66 +7,93 @@ describe Waistband::Index do
7
7
  let(:attrs) { {'ok' => {'yeah' => true}} }
8
8
 
9
9
  it "initializes values" do
10
- index.instance_variable_get('@stringify').should eql true
10
+ expect(index.instance_variable_get('@stringify')).to eql true
11
11
  end
12
12
 
13
13
  it "creates the index" do
14
- index.destroy!
15
- expect{ index.refresh! }.to raise_error(Waistband::Connection::Error::Resquest)
14
+ index.delete!
15
+ expect{ index.refresh }.to raise_error(Elasticsearch::Transport::Transport::Errors::NotFound)
16
16
 
17
17
  index.create!
18
- expect{ index.refresh! }.to_not raise_error
18
+ expect{ index.refresh }.to_not raise_error
19
19
  end
20
20
 
21
- it "destroys the index" do
22
- index.destroy!
23
- expect{ index.refresh! }.to raise_error(Waistband::Connection::Error::Resquest)
21
+ it "deletes the index" do
22
+ index.delete!
23
+ expect{ index.refresh }.to raise_error(Elasticsearch::Transport::Transport::Errors::NotFound)
24
24
  index.create!
25
25
  end
26
26
 
27
+ it "blows up when trying to delete an index that does not exist" do
28
+ index.delete!
29
+ expect { index.delete! }.to raise_error(::Waistband::Errors::IndexNotFound)
30
+ end
31
+
32
+ it "updates the index's mappings" do
33
+ index.refresh
34
+ response = index.update_mapping('event')
35
+ expect(response['acknowledged']).to be_true
36
+ end
37
+
27
38
  it "updates the index's settings" do
28
39
  index.refresh
29
- response = JSON.parse(index.update_settings!)
30
- response['ok'].should be_true
40
+ response = index.update_settings
41
+ expect(response['acknowledged']).to be_true
31
42
  end
32
43
 
33
- it "proxies to a query" do
34
- index.query.should be_a Waistband::Query
44
+ it "proxies to the client's search" do
45
+ result = index.search({})
46
+ expect(result).to be_a Waistband::SearchResults
47
+ expect(result.took).to be_present
48
+ expect(result.hits).to be_an Array
35
49
  end
36
50
 
37
51
  describe "storing" do
38
52
 
39
53
  it "stores data" do
40
- index.store!('__test_write', {'ok' => 'yeah'})
41
- index.read('__test_write').should eql({'ok' => 'yeah'})
54
+ expect(index.save('__test_write', {'ok' => 'yeah'})).to be_true
55
+ expect(index.read('__test_write')).to eql({
56
+ '_id' => '__test_write',
57
+ '_index' => 'events_test',
58
+ '_source' => {'ok' => 'yeah'},
59
+ '_type' => 'event',
60
+ '_version' => 1,
61
+ 'found' => true
62
+ })
42
63
  end
43
64
 
44
65
  it "data is stringified" do
45
- index.store!('__test_write', attrs)
46
- index.read('__test_write').should eql({"ok"=>"{\"yeah\"=>true}"})
66
+ index.save('__test_write', attrs)
67
+ expect(index.read('__test_write')[:_source]).to eql({"ok"=>"{\"yeah\"=>true}"})
47
68
  end
48
69
 
49
70
  it "data is indirectly accessible when not stringified" do
50
- index2.store!('__test_not_string', attrs)
51
- index2.read('__test_not_string')[:ok][:yeah].should eql true
71
+ index2.save('__test_not_string', attrs)
72
+ expect(index2.read('__test_not_string')[:_source][:ok][:yeah]).to eql true
52
73
  end
53
74
 
54
75
  it "deletes data" do
55
- index.store!('__test_write', attrs)
56
- index.delete!('__test_write')
57
- index.read('__test_write').should be_nil
76
+ index.save('__test_write', attrs)
77
+ index.destroy('__test_write')
78
+ expect(index.read('__test_write')).to be_nil
58
79
  end
59
80
 
60
81
  it "returns nil on 404" do
61
- index.read('__not_here').should be_nil
82
+ expect(index.read('__not_here')).to be_nil
83
+ end
84
+
85
+ it "blows up on 404 when using the bang method" do
86
+ expect {
87
+ index.read!('__not_here')
88
+ }.to raise_error(Elasticsearch::Transport::Transport::Errors::NotFound)
62
89
  end
63
90
 
64
91
  it "doesn't mix data between two indexes" do
65
- index.store!('__test_write', {'data' => 'index_1'})
66
- index2.store!('__test_write', {'data' => 'index_2'})
92
+ index.save('__test_write', {'data' => 'index_1'})
93
+ index2.save('__test_write', {'data' => 'index_2'})
67
94
 
68
- index.read('__test_write').should eql({'data' => 'index_1'})
69
- index2.read('__test_write').should eql({'data' => 'index_2'})
95
+ expect(index.read('__test_write')[:_source]).to eql({'data' => 'index_1'})
96
+ expect(index2.read('__test_write')[:_source]).to eql({'data' => 'index_2'})
70
97
  end
71
98
 
72
99
  end
@@ -76,26 +103,24 @@ describe Waistband::Index do
76
103
  let(:sharded_index) { Waistband::Index.new('events', subs: %w(2013 01)) }
77
104
 
78
105
  it "permits subbing the index" do
79
- sharded_index.name.should eql 'events__2013_01'
80
- sharded_index.config_name.should eql 'events_test__2013_01'
106
+ expect(sharded_index.send(:config_name)).to eql 'events_test__2013_01'
81
107
  end
82
108
 
83
109
  it "permits sharding into singles" do
84
110
  index = Waistband::Index.new 'events', subs: '2013'
85
- index.name.should eql 'events__2013'
86
- index.config_name.should eql 'events_test__2013'
111
+ expect(index.send(:config_name)).to eql 'events_test__2013'
87
112
  end
88
113
 
89
114
  it "retains the same options from the parent index config" do
90
115
  config = sharded_index.send(:config)
91
116
 
92
- sharded_index.base_config_name.should eql 'events_test'
93
- config['stringify'].should be_true
94
- config['settings'].should be_present
117
+ expect(sharded_index.send(:base_config_name)).to eql 'events_test'
118
+ expect(config['stringify']).to be_true
119
+ expect(config['settings']).to be_present
95
120
  end
96
121
 
97
122
  it "creates the sharded index with the same mappings as the parent" do
98
- sharded_index.destroy
123
+ sharded_index.delete!
99
124
 
100
125
  expect {
101
126
  sharded_index.create!
@@ -106,7 +131,7 @@ describe Waistband::Index do
106
131
 
107
132
  it "gets a default name that makes sense for the index when not defined" do
108
133
  index = Waistband::Index.new 'events_no_name', subs: %w(2013 01)
109
- index.config_name.should eql 'events_no_name_test__2013_01'
134
+ expect(index.send(:config_name)).to eql 'events_no_name_test__2013_01'
110
135
  end
111
136
 
112
137
  end
@@ -117,7 +142,22 @@ describe Waistband::Index do
117
142
 
118
143
  it "gets a default name that makes sense for the index when not defined" do
119
144
  index = Waistband::Index.new 'events_no_name'
120
- index.base_config_name.should eql 'events_no_name_test'
145
+ expect(index.send(:base_config_name)).to eql 'events_no_name_test'
146
+ end
147
+
148
+ end
149
+
150
+ describe 'aliases' do
151
+
152
+ it "returns the alias name with the env" do
153
+ expect(index.send(:full_alias_name, 'all_events')).to eql 'all_events_test'
154
+ end
155
+
156
+ it "if the index has a custom name, the alias name doesn't automatically append the env" do
157
+ index.stub(:config).and_return({
158
+ 'name' => 'super_custom'
159
+ })
160
+ expect(index.send(:full_alias_name, 'all_events')).to eql 'all_events'
121
161
  end
122
162
 
123
163
  end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::Waistband::Result do
4
+
5
+ let(:result_hash) do
6
+ {
7
+ "_index" => "bus_events",
8
+ "_type" => "bus_event",
9
+ "_id" => "a2081c0ce39b25d50b0a4be3c116ee7f",
10
+ "_score" => nil,
11
+ "_source" => {
12
+ "bus_event_type" => "task_opened",
13
+ "timeline_event" => "true",
14
+ "_message" => "true"
15
+ },
16
+ "sort" => [nil]
17
+ }
18
+ end
19
+
20
+ let(:result) { ::Waistband::Result.new(result_hash) }
21
+
22
+ it "provides accessors for all default fields" do
23
+ expect(result._id).to eql 'a2081c0ce39b25d50b0a4be3c116ee7f'
24
+ expect(result._score).to be_nil
25
+ expect(result._type).to eql 'bus_event'
26
+ expect(result._index).to eql 'bus_events'
27
+ expect(result.sort).to eql([nil])
28
+ expect(result._source).to eql({
29
+ "bus_event_type" => "task_opened",
30
+ "timeline_event" => "true",
31
+ "_message" => "true"
32
+ })
33
+ end
34
+
35
+ it "provides method missing interface for the _source hash" do
36
+ expect(result.bus_event_type).to eql 'task_opened'
37
+ expect(result.timeline_event).to be_true
38
+ expect(result._message).to be_true
39
+ end
40
+
41
+ end
@@ -0,0 +1,124 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::Waistband::SearchResults do
4
+
5
+ let(:search_hash) do
6
+ {
7
+ "took" => 1,
8
+ "timed_out" => false,
9
+ "_shards" => {
10
+ "total" => 12,
11
+ "successful" => 12,
12
+ "failed" => 0
13
+ },
14
+ "hits" => {
15
+ "total" => 1,
16
+ "max_score" => nil,
17
+ "hits" => [{
18
+ "_index" => "bus_events",
19
+ "_type" => "bus_event",
20
+ "_id" => "a2081c0ce39b25d50b0a4be3c116ee7f",
21
+ "_score" => nil,
22
+ "_source" => {
23
+ "bus_event_type" => "task_opened",
24
+ "timeline_event" => "true",
25
+ "_message" => "true"
26
+ },
27
+ "sort" => [nil]
28
+ }]
29
+ }
30
+ }
31
+ end
32
+
33
+ let(:results) { ::Waistband::SearchResults.new(search_hash) }
34
+
35
+ it "provides a method interface for the results hash array" do
36
+ expect(results.took).to eql 1
37
+ expect(results.timed_out).to be_false
38
+ expect(results._shards).to eql({
39
+ "total" => 12,
40
+ "successful" => 12,
41
+ "failed" => 0
42
+ })
43
+ end
44
+
45
+ it "provides a shortcut to get the total number of hits" do
46
+ expect(results.total_results).to eql 1
47
+ end
48
+
49
+ it "provides a shortcut to get the hits directly" do
50
+ expect(results.hits).to be_an Array
51
+ hit = results.hits.first
52
+
53
+ expect(hit['_id']).to eql 'a2081c0ce39b25d50b0a4be3c116ee7f'
54
+ expect(hit['_source']['bus_event_type']).to eql 'task_opened'
55
+ end
56
+
57
+ it "maps hits to results" do
58
+ expect(results.results).to be_an Array
59
+
60
+ result = results.results.first
61
+
62
+ expect(result).to be_a ::Waistband::Result
63
+ expect(result._id).to eql 'a2081c0ce39b25d50b0a4be3c116ee7f'
64
+ expect(result._score).to be_nil
65
+ expect(result._type).to eql 'bus_event'
66
+ expect(result._index).to eql 'bus_events'
67
+ expect(result.sort).to eql([nil])
68
+ expect(result._source).to eql({
69
+ "bus_event_type" => "task_opened",
70
+ "timeline_event" => "true",
71
+ "_message" => "true"
72
+ })
73
+ expect(result.bus_event_type).to eql 'task_opened'
74
+ expect(result.timeline_event).to be_true
75
+ expect(result._message).to be_true
76
+ end
77
+
78
+ describe '#paginated_hits' do
79
+
80
+ it "provides a paginated array from Kaminari if Kaminari is defined" do
81
+ load 'active_support/concern.rb'
82
+ load 'kaminari/config.rb'
83
+ load 'kaminari/models/page_scope_methods.rb'
84
+ load 'kaminari/models/configuration_methods.rb'
85
+ load 'kaminari/models/array_extension.rb'
86
+
87
+ expect(results.paginated_hits).to be_an ::Kaminari::PaginatableArray
88
+ expect(results.paginated_hits.total_pages).to eql 1
89
+ expect(results.paginated_hits.current_page).to eql 1
90
+ end
91
+
92
+ it "blows up when kaminari's not required" do
93
+ # if we've already required Kaminari from the test above, lets remove
94
+ Object.send(:remove_const, :Kaminari) if defined?(Kaminari)
95
+
96
+ expect { results.paginated_hits }.to raise_error RuntimeError, "Kaminari gem not found for pagination"
97
+ end
98
+
99
+ end
100
+
101
+ describe '#paginated_results' do
102
+
103
+ it "provides a paginated array from Kaminari if Kaminari is defined" do
104
+ load 'active_support/concern.rb'
105
+ load 'kaminari/config.rb'
106
+ load 'kaminari/models/page_scope_methods.rb'
107
+ load 'kaminari/models/configuration_methods.rb'
108
+ load 'kaminari/models/array_extension.rb'
109
+
110
+ expect(results.paginated_results).to be_an ::Kaminari::PaginatableArray
111
+ expect(results.paginated_results.total_pages).to eql 1
112
+ expect(results.paginated_results.current_page).to eql 1
113
+ end
114
+
115
+ it "blows up when kaminari's not required" do
116
+ # if we've already required Kaminari from the test above, lets remove
117
+ Object.send(:remove_const, :Kaminari) if defined?(Kaminari)
118
+
119
+ expect { results.paginated_results }.to raise_error RuntimeError, "Kaminari gem not found for pagination"
120
+ end
121
+
122
+ end
123
+
124
+ end