waistband 0.7.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec CHANGED
@@ -1 +1,2 @@
1
1
  --color
2
+ --debugger
data/README.md CHANGED
@@ -56,7 +56,6 @@ You'll need a separate config file for each index you use, containing the index
56
56
  ```yml
57
57
  # #{APP_DIR}/config/waistband/waistband_search.yml
58
58
  development:
59
- name: search
60
59
  stringify: false
61
60
  settings:
62
61
  index:
@@ -69,10 +68,10 @@ development:
69
68
 
70
69
  ## List of config settings:
71
70
 
72
- * `name`: name of the index. You can (and probably should) have a different name for the index for your test environment.
73
- * `stringify`: determines wether whatever is stored into the index is going to be converted to a string before storage. Usually false unless you need it to be true for specific cases, like if for some `key => value` pairs the value is of different types some times.
74
71
  * `settings`: settings for the Elastic Search index. Refer to the ["admin indices update settings"](http://www.elasticsearch.org/guide/reference/api/admin-indices-update-settings/) document for more info.
75
72
  * `mappings`: the index mappings. More often than not you'll want to include all of the document attribute, so you'll do something like in the example above. For more info, refer to the [mapping reference]("http://www.elasticsearch.org/guide/reference/mapping/").
73
+ * `name`: optional - name of the index. You can (and probably should) have a different name for the index for your test environment. If not specified, it defaults to the name of the yml file minus the `waistband_` portion, so in the above example, the index name would become `search_#{env}`, where env is your environment variable as defined in `Waistband::Configuration#setup` (determined by `RAILS_ENV` or `RACK_ENV`).
74
+ * `stringify`: optional - determines wether whatever is stored into the index is going to be converted to a string before storage. Usually false unless you need it to be true for specific cases, like if for some `key => value` pairs the value is of different types some times.
76
75
 
77
76
  ## Initializer
78
77
 
@@ -114,6 +113,8 @@ When writing tests, it would generally be advisable to destroy and create the in
114
113
  Waistband::Index.new('search').refresh
115
114
  ```
116
115
 
116
+ Note: most index methods such as `create`, `destroy`, `read`, etc, have an equivalent bang method (`destroy!`) that will actually throw an exception if something goes wrong. For example, `destroy` will return nil if the index doesn't exist, but will raise any other unrelated exceptions, whereas `destroy!` will raise even the Index Not Found exception.
117
+
117
118
  #### Writing, reading and deleting from an index
118
119
 
119
120
  ```ruby
@@ -168,6 +169,32 @@ For paginating the results, you can use the `#paginated_results` method, which r
168
169
 
169
170
  For more information and extra methods, take a peek into the class docs.
170
171
 
172
+ ### Sub-Indexes
173
+
174
+ Sometimes it can be useful to sub-divide your index into smaller indexes based on dates or other partitioning schemes. To do this, the `Index` class exposes the `subs` option on instantiation:
175
+
176
+ ```ruby
177
+ index = Waistband::Index.new('events', subs: %w(2013 01))
178
+ index.create!
179
+ ```
180
+
181
+ This creates the index `events__2013_01`, which in your application logic you could design to store all event data for Jan 2013. You'd do the same for Feb, etc., and when you no longer need one of the older ones, you could delete just that sub-index, instead of things getting more complicated.
182
+
183
+ ### Aliasing
184
+
185
+ Part of subbing is gonna be creating the correct aliases that group up your sub-indexes.
186
+
187
+ ```ruby
188
+ index = Waistband::Index.new('events', subs: %w(2013 01))
189
+ index.create!
190
+ index.alias!('my_super_events_alias')
191
+ => true
192
+ index.fetch_alias('my_super_events_alias')
193
+ => {"events__2013_01"=>{"aliases"=>{"my_super_events_alias"=>{}}}}
194
+ ```
195
+
196
+ The `alias!` methods receives a param to define the alias name.
197
+
171
198
  ## Contributing
172
199
 
173
200
  1. Fork it
@@ -9,6 +9,7 @@ module Waistband
9
9
  include Singleton
10
10
 
11
11
  attr_accessor :config_dir
12
+ attr_reader :env
12
13
 
13
14
  def initialize
14
15
  @yml_config = {}
@@ -6,52 +6,170 @@ require 'active_support/core_ext/hash/indifferent_access'
6
6
  module Waistband
7
7
  class Connection
8
8
 
9
- class NoMoreServers < Exception; end
9
+ ##########
10
+ # Errors #
11
+ ##########
10
12
 
11
- def initialize(options = {})
13
+ module Error
14
+
15
+ class Resquest < Exception
16
+ attr_accessor :result, :kind
17
+
18
+ def index_already_exists?
19
+ kind == "IndexAlreadyExistsException"
20
+ end
21
+
22
+ def alias_with_name_exists?
23
+ kind == "InvalidIndexNameException" && !!message.match("an alias with the same name already exists")
24
+ end
25
+
26
+ def index_missing?
27
+ kind == "IndexMissingException"
28
+ end
29
+
30
+ def key_missing?
31
+ kind == "KeyMissing"
32
+ end
33
+
34
+ def alias_not_ok?
35
+ kind == "AliasNotOk"
36
+ end
37
+ end
38
+
39
+ class NoMoreServers < Exception; end
40
+
41
+ end
42
+
43
+ ####################
44
+ # Instance methods #
45
+ ####################
46
+
47
+ def initialize(index, options = {})
48
+ @index = index
12
49
  @blacklist = []
13
50
  @retry_on_fail = options.fetch :retry_on_fail, true
14
51
  @orderly = options.fetch :orderly, false
15
52
  pick_server
16
53
  end
17
54
 
18
- def create!(index)
19
- execute! 'post', relative_url_for_index(index), index_json(index)
20
- rescue RestClient::BadRequest => ex
21
- nil
55
+ def mapping!
56
+ execute! 'get', "#{@index.config_name}/_mapping"
57
+ end
58
+
59
+ def mapping
60
+ mapping!
61
+ rescue Waistband::Connection::Error::Resquest => ex
62
+ raise ex unless ex.index_missing?
63
+ end
64
+
65
+ def exists?
66
+ !!mapping.present?
67
+ end
68
+
69
+ def create!
70
+ execute! 'post', @index.config_name, @index.config_json
22
71
  end
23
72
 
24
- def destroy!(index)
25
- execute! 'delete', relative_url_for_index(index)
26
- rescue RestClient::ResourceNotFound => ex
27
- nil
73
+ def create
74
+ create!
75
+ rescue Waistband::Connection::Error::Resquest => ex
76
+ raise ex if !ex.index_already_exists? && ! ex.alias_with_name_exists?
28
77
  end
29
78
 
30
- def update_settings!(index)
31
- execute! 'put', "#{relative_url_for_index(index)}/_settings", settings_json(index)
79
+ def destroy
80
+ destroy!
81
+ rescue ::Waistband::Connection::Error::Resquest => ex
82
+ raise ex unless ex.index_missing?
32
83
  end
33
84
 
34
- def refresh(index)
35
- execute! 'post', "#{relative_url_for_index(index)}/_refresh", {}
85
+ def destroy!
86
+ execute! 'delete', @index.config_name
36
87
  end
37
88
 
38
- def read(index, key)
39
- fetched = execute! 'get', relative_url_for_key(index, key)
40
- JSON.parse(fetched)['_source'].with_indifferent_access
41
- rescue RestClient::ResourceNotFound => ex
42
- nil
89
+ def update_settings!
90
+ execute! 'put', "#{@index.config_name}/_settings", settings_json
43
91
  end
44
92
 
45
- def put(index, key, data)
46
- execute! 'put', relative_url_for_key(index, key), data.to_json
93
+ def update_settings
94
+ update_settings!
95
+ rescue ::Waistband::Connection::Error::Resquest => ex
96
+ raise ex unless ex.index_missing?
47
97
  end
48
98
 
49
- def delete!(index, key)
50
- execute! 'delete', relative_url_for_key(index, key)
99
+ def refresh!
100
+ execute! 'post', "#{@index.config_name}/_refresh", {}
51
101
  end
52
102
 
53
- def search_url_for_index(index)
54
- "#{url}/#{relative_url_for_index(index)}/_search"
103
+ def refresh
104
+ refresh!
105
+ rescue ::Waistband::Connection::Error::Resquest => ex
106
+ raise ex unless ex.index_missing?
107
+ end
108
+
109
+ def alias!(alias_name)
110
+ alias_name = full_alias_name alias_name
111
+ fetched = execute! 'put', "#{@index.config_name}/_alias/#{alias_name}"
112
+ parsed = JSON.parse(fetched)
113
+
114
+ error_with('Alias not OK', result: parsed, kind: 'AliasNotOk') unless parsed['ok'] == true
115
+
116
+ true
117
+ end
118
+
119
+ def alias(alias_name)
120
+ alias! alias_name
121
+ rescue ::Waistband::Connection::Error::Resquest => ex
122
+ raise ex unless ex.alias_not_ok?
123
+ end
124
+
125
+ def fetch_alias(alias_name)
126
+ alias_name = full_alias_name alias_name
127
+ fetched = execute! 'get', "#{@index.config_name}/_alias/#{alias_name}"
128
+ JSON.parse(fetched)
129
+ end
130
+
131
+ def full_alias_name(alias_name)
132
+ name = alias_name
133
+ name << "_#{@index.env}" unless @index.custom_name?
134
+ name
135
+ end
136
+
137
+ def read!(key)
138
+ fetched = execute! 'get', relative_url_for_key(key)
139
+ parsed = JSON.parse(fetched)
140
+
141
+ error_with('Key not found', result: parsed, kind: 'KeyMissing') if parsed['exists'] == false
142
+
143
+ parsed['_source'].with_indifferent_access
144
+ end
145
+
146
+ def read(key)
147
+ read! key
148
+ rescue ::Waistband::Connection::Error::Resquest => ex
149
+ raise ex unless ex.key_missing?
150
+ end
151
+
152
+ def put(key, data)
153
+ execute! 'put', relative_url_for_key(key), data.to_json
154
+ end
155
+
156
+ def delete!(key)
157
+ fetched = execute! 'delete', relative_url_for_key(key)
158
+ parsed = JSON.parse(fetched)
159
+
160
+ error_with('Key not found', result: parsed, kind: 'KeyMissing') if parsed['found'] == false
161
+
162
+ true
163
+ end
164
+
165
+ def delete(key)
166
+ delete! key
167
+ rescue ::Waistband::Connection::Error::Resquest => ex
168
+ raise ex unless ex.key_missing?
169
+ end
170
+
171
+ def search_url
172
+ "#{url}/#{@index.config_name}/_search"
55
173
  end
56
174
 
57
175
  private
@@ -60,7 +178,15 @@ module Waistband
60
178
  full_url = "#{url}/#{relative_url}"
61
179
 
62
180
  Timeout::timeout ::Waistband.config.timeout do
63
- RestClient.send method_name, full_url, data
181
+ RestClient.send method_name, full_url, data do |response, request, result|
182
+ response_hash = JSON.parse(response)
183
+
184
+ if msg = response_hash['error']
185
+ error_with(msg, result: result, kind: msg.split('[').first)
186
+ end
187
+
188
+ response
189
+ end
64
190
  end
65
191
  rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET
66
192
  # something's wrong, lets blacklist this sucker
@@ -68,35 +194,23 @@ module Waistband
68
194
  retry if @retry_on_fail
69
195
  end
70
196
 
71
- def relative_url_for_key(index, key)
72
- "#{relative_url_for_index(index)}/#{index.singularize}/#{key}"
73
- end
74
-
75
- def relative_url_for_index(index)
76
- "#{index_name(index)}"
197
+ def relative_url_for_key(key)
198
+ "#{@index.config_name}/#{@index.base_name.singularize}/#{key}"
77
199
  end
78
200
 
79
201
  def url
80
202
  "#{@server['host']}:#{@server['port']}"
81
203
  end
82
204
 
83
- def index_name(index)
84
- config(index)['name']
85
- end
86
-
87
- def index_json(index)
88
- config(index).except('name', 'stringify').to_json
205
+ def index_json
206
+ @index.config.except('name', 'stringify').to_json
89
207
  end
90
208
 
91
- def settings_json(index)
92
- settings = config(index)['settings']['index'].except('number_of_shards')
209
+ def settings_json
210
+ settings = @index.config['settings']['index'].except('number_of_shards')
93
211
  {index: settings}.to_json
94
212
  end
95
213
 
96
- def config(index)
97
- Waistband.config.index(index)
98
- end
99
-
100
214
  def blacklist!(server)
101
215
  @blacklist << server['_id'] unless @blacklist.include? server['_id']
102
216
  @blacklist
@@ -108,7 +222,7 @@ module Waistband
108
222
  @server = next_server
109
223
 
110
224
  unless @server
111
- raise ::Waistband::Connection::NoMoreServers.new "No available servers remain"
225
+ raise ::Waistband::Connection::Error::NoMoreServers.new "No available servers remain"
112
226
  end
113
227
 
114
228
  @server
@@ -119,6 +233,13 @@ module Waistband
119
233
  available_servers.sample
120
234
  end
121
235
 
236
+ def error_with(msg, options = {})
237
+ exception = ::Waistband::Connection::Error::Resquest.new(msg)
238
+ exception.result = options[:result]
239
+ exception.kind = options[:kind]
240
+ raise exception
241
+ end
242
+
122
243
  def available_servers
123
244
  ::Waistband.config.servers.reject {|server| @blacklist.include? server['_id']}
124
245
  end
@@ -1,75 +1,92 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'active_support/core_ext/hash/keys'
3
+
1
4
  module Waistband
2
5
  class Index
3
6
 
4
- def initialize(index)
5
- @index = index
6
- @index_name = config['name']
7
- @stringify = config['stringify']
8
- end
7
+ delegate :create!, :create, :destroy!, :destroy,
8
+ :update_settings!, :update_settings,
9
+ :delete!, :delete, :read!, :read,
10
+ :alias, :alias!,
11
+ :fetch_alias, :mapping, :exists?,
12
+ :refresh!, :refresh,
13
+ :search_url,
14
+ to: :connection
9
15
 
10
- # create the index
11
- def create!
12
- connection.create! @index
13
- end
16
+ attr_reader :base_name
14
17
 
15
- # destroy the index
16
- def destroy!
17
- connection.destroy! @index
18
- end
18
+ def initialize(index, options = {})
19
+ options = options.stringify_keys
19
20
 
20
- def update_settings!
21
- connection.update_settings! @index
22
- end
21
+ @index = index
22
+ @base_name = index
23
+ @stringify = config['stringify']
23
24
 
24
- # refresh the index
25
- def refresh
26
- connection.refresh @index
25
+ @subs = [options['subs']] if options['subs'].present?
26
+ @subs = @subs.flatten if @subs.is_a?(Array)
27
27
  end
28
28
 
29
29
  def store!(key, data)
30
30
  # map everything to strings
31
31
  if @stringify
32
32
  original_data = data
33
-
34
- if data.is_a? Array
35
- data = ::Waistband::StringifiedArray.new data
36
- elsif data.is_a? Hash
37
- data = ::Waistband::StringifiedHash.new_from data
38
- end
39
-
40
- data = data.stringify_all if data.respond_to? :stringify_all
33
+ data = stringify_all data
41
34
  end
42
35
 
43
- result = connection.put @index, key, data
44
- data = original_data if @stringify
36
+ result = connection.put key, data
37
+ data = original_data if @stringify
45
38
 
46
39
  result
47
40
  end
48
41
 
49
- def delete!(key)
50
- connection.delete! @index, key
42
+ def query(options = {})
43
+ ::Waistband::Query.new self, options
51
44
  end
52
45
 
53
- def read(key)
54
- connection.read @index, key
46
+ def name
47
+ @subs ? "#{@index}__#{@subs.join('_')}" : @index
55
48
  end
56
49
 
57
- def query(options = {})
58
- ::Waistband::Query.new @index, options
50
+ def config_name
51
+ @subs ? "#{base_config_name}__#{@subs.join('_')}" : base_config_name
52
+ end
53
+
54
+ def config_json
55
+ config.except('name', 'stringify').to_json
56
+ end
57
+
58
+ def base_config_name
59
+ return config['name'] if config['name']
60
+ "#{@base_name}_#{env}"
61
+ end
62
+
63
+ def custom_name?
64
+ !!config['name']
65
+ end
66
+
67
+ def config
68
+ Waistband.config.index @index
59
69
  end
60
70
 
61
- def search_url
62
- connection.search_url_for_index @index
71
+ def env
72
+ Waistband.config.env
63
73
  end
64
74
 
65
75
  private
66
76
 
67
- def connection
68
- ::Waistband::Connection.new
77
+ def stringify_all(data)
78
+ data = if data.is_a? Array
79
+ ::Waistband::StringifiedArray.new data
80
+ elsif data.is_a? Hash
81
+ ::Waistband::StringifiedHash.new_from data
82
+ end
83
+
84
+ data = data.stringify_all if data.respond_to? :stringify_all
85
+ data
69
86
  end
70
87
 
71
- def config
72
- Waistband.config.index @index
88
+ def connection
89
+ ::Waistband::Connection.new self
73
90
  end
74
91
 
75
92
  # /private
@@ -46,11 +46,7 @@ module Waistband
46
46
  end
47
47
 
48
48
  def url
49
- index.search_url
50
- end
51
-
52
- def index
53
- Waistband::Index.new(@index)
49
+ @index.search_url
54
50
  end
55
51
 
56
52
  def execute!
@@ -1,3 +1,3 @@
1
1
  module Waistband
2
- VERSION = "0.7.4"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -1,9 +1,8 @@
1
1
  development: &DEV
2
- name: events
3
2
  stringify: true
4
3
  settings:
5
4
  index:
6
- number_of_shards: 4
5
+ number_of_shards: 1
7
6
  number_of_replicas: 1
8
7
  mappings:
9
8
  event:
@@ -12,4 +11,3 @@ development: &DEV
12
11
 
13
12
  test:
14
13
  <<: *DEV
15
- name: events_test
@@ -0,0 +1,13 @@
1
+ development: &DEV
2
+ stringify: true
3
+ settings:
4
+ index:
5
+ number_of_shards: 1
6
+ number_of_replicas: 1
7
+ mappings:
8
+ event:
9
+ _source:
10
+ includes: ["*"]
11
+
12
+ test:
13
+ <<: *DEV
@@ -1,9 +1,9 @@
1
1
  development: &DEV
2
- name: waistband_geo
3
2
  stringify: false
4
3
  settings:
5
4
  index:
6
- number_of_shards: 4
5
+ number_of_shards: 1
6
+ number_of_replicas: 1
7
7
  mappings:
8
8
  geo:
9
9
  _source:
@@ -16,4 +16,3 @@ development: &DEV
16
16
 
17
17
  test:
18
18
  <<: *DEV
19
- name: waistband_geo_test
@@ -1,9 +1,9 @@
1
1
  development: &DEV
2
- name: search
3
2
  stringify: false
4
3
  settings:
5
4
  index:
6
- number_of_shards: 4
5
+ number_of_shards: 1
6
+ number_of_replicas: 1
7
7
  mappings:
8
8
  event:
9
9
  _source:
@@ -11,4 +11,3 @@ development: &DEV
11
11
 
12
12
  test:
13
13
  <<: *DEV
14
- name: search_test
@@ -12,14 +12,12 @@ describe Waistband::Configuration do
12
12
 
13
13
  it "loads indexes config" do
14
14
  config.index('search').should be_a Hash
15
- config.index('search')['name'].should eql 'search_test'
16
- config.index('search')['settings']['index']['number_of_shards'].should eql 4
15
+ config.index('search')['settings']['index']['number_of_shards'].should eql 1
17
16
  end
18
17
 
19
18
  it "loads multiple indexes config" do
20
19
  config.index('events').should be_a Hash
21
- config.index('events')['name'].should eql 'events_test'
22
- config.index('events')['settings']['index']['number_of_shards'].should eql 4
20
+ config.index('events')['settings']['index']['number_of_shards'].should eql 1
23
21
  end
24
22
 
25
23
  describe '#servers' do
@@ -2,24 +2,227 @@ require 'spec_helper'
2
2
 
3
3
  describe Waistband::Connection do
4
4
 
5
- let(:connection) { Waistband::Connection.new }
5
+ let(:connection) { Waistband::Connection.new(index) }
6
+ let(:search_connection) { Waistband::Connection.new(index_search) }
7
+ let(:index) { ::Waistband::Index.new('events') }
8
+ let(:index_search) { ::Waistband::Index.new('search') }
6
9
 
7
10
  def blacklist_server!
8
11
  connection.send(:blacklist!, Waistband.config.servers.first)
9
12
  end
10
13
 
11
14
  it "constructs the settings json" do
12
- connection.send(:settings_json, 'events').should eql '{"index":{"number_of_replicas":1}}'
15
+ connection.send(:settings_json).should eql '{"index":{"number_of_replicas":1}}'
13
16
  end
14
17
 
15
18
  it "constructs the index json" do
16
- connection.send(:index_json, 'events').should eql '{"settings":{"index":{"number_of_shards":4,"number_of_replicas":1}},"mappings":{"event":{"_source":{"includes":["*"]}}}}'
19
+ connection.send(:index_json).should eql '{"settings":{"index":{"number_of_shards":1,"number_of_replicas":1}},"mappings":{"event":{"_source":{"includes":["*"]}}}}'
20
+ end
21
+
22
+ describe 'urls for subindexes' do
23
+
24
+ let(:index) { ::Waistband::Index.new('events', subs: %w(2013 01)) }
25
+ let(:connection) { Waistband::Connection.new(index, orderly: true) }
26
+
27
+ describe '#destroy!' do
28
+
29
+ it "targets the correct url" do
30
+ RestClient.should_receive(:send).with('delete', "http://localhost:9200/events_test__2013_01", nil).once
31
+ connection.destroy!
32
+ end
33
+
34
+ end
35
+
36
+ describe '#create!' do
37
+
38
+ it "targets the correct url" do
39
+ RestClient.should_receive(:send).with('post', "http://localhost:9200/events_test__2013_01", index.config_json).once
40
+ connection.create!
41
+ end
42
+
43
+ end
44
+
45
+ describe '#update_settings!' do
46
+
47
+ it "targets the correct url" do
48
+ RestClient.should_receive(:send).with('put', "http://localhost:9200/events_test__2013_01/_settings", connection.send(:settings_json)).once
49
+ connection.update_settings!
50
+ end
51
+
52
+ end
53
+
54
+ describe '#refresh!' do
55
+
56
+ it "targets the correct url" do
57
+ RestClient.should_receive(:send).with('post', "http://localhost:9200/events_test__2013_01/_refresh", {}).once
58
+ connection.refresh!
59
+ end
60
+
61
+ end
62
+
63
+ describe '#read' do
64
+
65
+ it "targets the correct url" do
66
+ RestClient.should_receive(:send).with('get', "http://localhost:9200/events_test__2013_01/event/my_key", nil).once.and_call_original
67
+ connection.read 'my_key'
68
+ end
69
+
70
+ end
71
+
72
+ describe '#put' do
73
+
74
+ it "targets the correct url" do
75
+ RestClient.should_receive(:send).with('put', "http://localhost:9200/events_test__2013_01/event/my_key", {oh_yeah: true}.to_json).once.and_call_original
76
+ connection.put 'my_key', {oh_yeah: true}
77
+ end
78
+
79
+ end
80
+
81
+ describe '#delete!' do
82
+
83
+ it "targets the correct url" do
84
+ RestClient.should_receive(:send).with('delete', "http://localhost:9200/events_test__2013_01/event/my_key", nil).once.and_call_original
85
+ connection.delete 'my_key'
86
+ end
87
+
88
+ end
89
+
90
+ describe '#search_url' do
91
+
92
+ it "targets the correct url" do
93
+ connection.search_url.should eql "http://localhost:9200/events_test__2013_01/_search"
94
+ end
95
+
96
+ end
97
+
98
+ describe '#alias!' do
99
+
100
+ it "targets the correct url with a specific alias_name" do
101
+ RestClient.should_receive(:send).with('put', "http://localhost:9200/events_test__2013_01/_alias/all_events_test", nil).once.and_call_original
102
+ connection.alias! 'all_events'
103
+ end
104
+
105
+ end
106
+
107
+ end
108
+
109
+ describe '#full_alias_name' do
110
+
111
+ it "returns the alias name with the env" do
112
+ connection.full_alias_name('all_events').should eql 'all_events_test'
113
+ end
114
+
115
+ it "if the index has a custom name, the alias name doesn't automatically append the env" do
116
+ connection.instance_variable_get('@index').stub(:config).and_return({
117
+ 'name' => 'super_custom'
118
+ })
119
+ connection.full_alias_name('all_events').should eql 'all_events'
120
+ end
121
+
122
+ end
123
+
124
+ describe 'urls' do
125
+
126
+ let(:connection) { Waistband::Connection.new(index, orderly: true) }
127
+
128
+ describe '#destroy!' do
129
+
130
+ it "targets the correct url" do
131
+ RestClient.should_receive(:send).with('delete', "http://localhost:9200/events_test", nil).once
132
+ connection.destroy!
133
+ end
134
+
135
+ end
136
+
137
+ describe '#create!' do
138
+
139
+ it "targets the correct url" do
140
+ RestClient.should_receive(:send).with('post', "http://localhost:9200/events_test", index.config_json).once
141
+ connection.create!
142
+ end
143
+
144
+ end
145
+
146
+ describe '#update_settings!' do
147
+
148
+ it "targets the correct url" do
149
+ RestClient.should_receive(:send).with('put', "http://localhost:9200/events_test/_settings", connection.send(:settings_json)).once
150
+ connection.update_settings!
151
+ end
152
+
153
+ end
154
+
155
+ describe '#refresh!' do
156
+
157
+ it "targets the correct url" do
158
+ RestClient.should_receive(:send).with('post', "http://localhost:9200/events_test/_refresh", {}).once
159
+ connection.refresh!
160
+ end
161
+
162
+ end
163
+
164
+ describe '#read' do
165
+
166
+ it "targets the correct url" do
167
+ RestClient.should_receive(:send).with('get', "http://localhost:9200/events_test/event/my_key", nil).once.and_call_original
168
+ connection.read 'my_key'
169
+ end
170
+
171
+ end
172
+
173
+ describe '#put' do
174
+
175
+ it "targets the correct url" do
176
+ RestClient.should_receive(:send).with('put', "http://localhost:9200/events_test/event/my_key", {oh_yeah: true}.to_json).once.and_call_original
177
+ connection.put 'my_key', {oh_yeah: true}
178
+ end
179
+
180
+ end
181
+
182
+ describe '#delete!' do
183
+
184
+ it "targets the correct url" do
185
+ connection.put 'my_key', {oh_yeah: true}
186
+
187
+ RestClient.should_receive(:send).with('delete', "http://localhost:9200/events_test/event/my_key", nil).once.and_call_original
188
+ connection.delete! 'my_key'
189
+ end
190
+
191
+ end
192
+
193
+ describe '#search_url' do
194
+
195
+ it "targets the correct url" do
196
+ connection.search_url.should eql "http://localhost:9200/events_test/_search"
197
+ end
198
+
199
+ end
200
+
201
+ describe '#alias!' do
202
+
203
+ it "targets the correct url with a specific alias_name" do
204
+ RestClient.should_receive(:send).with('put', "http://localhost:9200/events_test/_alias/all_events_test", nil).once.and_call_original
205
+ connection.alias! 'all_events'
206
+ end
207
+
208
+ end
209
+
210
+ end
211
+
212
+ describe '#alias!' do
213
+
214
+ it "creates an alias for the index" do
215
+ connection.alias! 'all_events'
216
+ aliases = connection.fetch_alias 'all_events'
217
+ aliases.should eql({"events_test"=>{"aliases"=>{"all_events_test"=>{}}}})
218
+ end
219
+
17
220
  end
18
221
 
19
222
  describe '#execute!' do
20
223
 
21
224
  it "wraps directly to rest client" do
22
- connection = Waistband::Connection.new(orderly: true)
225
+ connection = Waistband::Connection.new(index, orderly: true)
23
226
 
24
227
  RestClient.should_receive(:get).with('http://localhost:9200/somekey', nil).once
25
228
  connection.send(:execute!, 'get', 'somekey')
@@ -29,7 +232,7 @@ describe Waistband::Connection do
29
232
 
30
233
  [Timeout::Error, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET].each do |exception|
31
234
  it "blacklists the server when #{exception}" do
32
- connection = Waistband::Connection.new(retry_on_fail: false)
235
+ connection = Waistband::Connection.new(index, retry_on_fail: false)
33
236
 
34
237
  RestClient.should_receive(:get).with(kind_of(String), nil).and_raise exception
35
238
  connection.send(:execute!, 'get', 'somekey')
@@ -54,8 +257,8 @@ describe Waistband::Connection do
54
257
  ]
55
258
  )
56
259
 
57
- connection = Waistband::Connection.new(orderly: true)
58
- expect { connection.refresh('events') }.to_not raise_error
260
+ connection = Waistband::Connection.new(index, orderly: true)
261
+ expect { connection.refresh }.to_not raise_error
59
262
 
60
263
  connection.instance_variable_get('@blacklist').should eql ['567890a5ce74182e5cd123e299993ab510c56123']
61
264
  end
@@ -67,7 +270,7 @@ describe Waistband::Connection do
67
270
  expect {
68
271
  connection.send(:execute!, 'get', 'somekey')
69
272
  }.to raise_error(
70
- Waistband::Connection::NoMoreServers,
273
+ Waistband::Connection::Error::NoMoreServers,
71
274
  "No available servers remain"
72
275
  )
73
276
  end
@@ -79,27 +282,15 @@ describe Waistband::Connection do
79
282
  describe '#relative_url_for_key' do
80
283
 
81
284
  it "returns the relative url for a key" do
82
- url = connection.send(:relative_url_for_key, 'search', 'key123')
285
+ url = search_connection.send(:relative_url_for_key, 'key123')
83
286
  url.should match /^search_test\/search\/key123$/
84
287
 
85
- url = connection.send(:relative_url_for_key, 'events', '9986')
288
+ url = connection.send(:relative_url_for_key, '9986')
86
289
  url.should match /^events_test\/event\/9986$/
87
290
  end
88
291
 
89
292
  end
90
293
 
91
- describe '#relative_url_for_index' do
92
-
93
- it "returns the url for an index" do
94
- url = connection.send(:relative_url_for_index, 'search')
95
- url.should match /^search_test$/
96
-
97
- url = connection.send(:relative_url_for_index, 'events')
98
- url.should match /^events_test$/
99
- end
100
-
101
- end
102
-
103
294
  describe '#url' do
104
295
 
105
296
  it "returns url string for the selected server" do
@@ -134,7 +325,7 @@ describe Waistband::Connection do
134
325
  expect {
135
326
  connection.send(:blacklist!, Waistband.config.servers.last)
136
327
  }.to raise_error(
137
- Waistband::Connection::NoMoreServers,
328
+ Waistband::Connection::Error::NoMoreServers,
138
329
  "No available servers remain"
139
330
  )
140
331
  end
@@ -188,38 +379,38 @@ describe Waistband::Connection do
188
379
 
189
380
  describe "storing" do
190
381
 
191
- let(:index) { ::Waistband::Index.new('events') }
192
- let(:index2) { ::Waistband::Index.new('search') }
193
- let(:attrs) { {'ok' => {'yeah' => true}} }
194
-
195
- before { IndexHelper.prepare! }
382
+ let(:attrs) { {'other_ok' => {'yeah' => true}} }
196
383
 
197
384
  it "stores data" do
198
- connection.put('events', '__test_write', {'ok' => 'yeah'})
385
+ connection.put('__test_write', {'ok' => 'yeah'})
199
386
  index.read('__test_write').should eql({'ok' => 'yeah'})
200
387
  end
201
388
 
202
389
  it "data is indirectly accessible" do
203
- connection.put('events', '__test_not_string', attrs)
204
- index.read('__test_not_string')[:ok][:yeah].should eql true
390
+ connection.put('__test_not_string', attrs)
391
+ index.read('__test_not_string')[:other_ok][:yeah].should eql true
205
392
  end
206
393
 
207
394
  it "deletes data" do
208
- connection.put('events', '__test_write', attrs)
209
- connection.delete!('events', '__test_write')
395
+ connection.put('__test_write', attrs)
396
+ connection.delete!('__test_write')
210
397
  index.read('__test_write').should be_nil
211
398
  end
212
399
 
400
+ it "blows up when trying to delete non-existent data" do
401
+ expect { connection.delete!('__test_write') }.to raise_error
402
+ end
403
+
213
404
  it "returns nil on 404" do
214
405
  index.read('__not_here').should be_nil
215
406
  end
216
407
 
217
408
  it "doesn't mix data between two indexes" do
218
- connection.put('events', '__test_write', {'data' => 'index_1'})
219
- connection.put('search', '__test_write', {'data' => 'index_2'})
409
+ connection.put('__test_write', {'data' => 'index_1'})
410
+ search_connection.put('__test_write', {'data' => 'index_2'})
220
411
 
221
412
  index.read('__test_write').should eql({'data' => 'index_1'})
222
- index2.read('__test_write').should eql({'data' => 'index_2'})
413
+ index_search.read('__test_write').should eql({'data' => 'index_2'})
223
414
  end
224
415
 
225
416
  end
@@ -7,21 +7,20 @@ describe Waistband::Index do
7
7
  let(:attrs) { {'ok' => {'yeah' => true}} }
8
8
 
9
9
  it "initializes values" do
10
- index.instance_variable_get('@index_name').should eql 'events_test'
11
10
  index.instance_variable_get('@stringify').should eql true
12
11
  end
13
12
 
14
13
  it "creates the index" do
15
14
  index.destroy!
16
- expect{ index.refresh }.to raise_error(RestClient::ResourceNotFound)
15
+ expect{ index.refresh! }.to raise_error(Waistband::Connection::Error::Resquest)
17
16
 
18
17
  index.create!
19
- expect{ index.refresh }.to_not raise_error
18
+ expect{ index.refresh! }.to_not raise_error
20
19
  end
21
20
 
22
21
  it "destroys the index" do
23
22
  index.destroy!
24
- expect{ index.refresh }.to raise_error(RestClient::ResourceNotFound)
23
+ expect{ index.refresh! }.to raise_error(Waistband::Connection::Error::Resquest)
25
24
  index.create!
26
25
  end
27
26
 
@@ -72,4 +71,55 @@ describe Waistband::Index do
72
71
 
73
72
  end
74
73
 
74
+ describe 'subindexes' do
75
+
76
+ let(:sharded_index) { Waistband::Index.new('events', subs: %w(2013 01)) }
77
+
78
+ 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'
81
+ end
82
+
83
+ it "permits sharding into singles" do
84
+ index = Waistband::Index.new 'events', subs: '2013'
85
+ index.name.should eql 'events__2013'
86
+ index.config_name.should eql 'events_test__2013'
87
+ end
88
+
89
+ it "retains the same options from the parent index config" do
90
+ config = sharded_index.send(:config)
91
+
92
+ sharded_index.base_config_name.should eql 'events_test'
93
+ config['stringify'].should be_true
94
+ config['settings'].should be_present
95
+ end
96
+
97
+ it "creates the sharded index with the same mappings as the parent" do
98
+ sharded_index.destroy
99
+
100
+ expect {
101
+ sharded_index.create!
102
+ }.to_not raise_error
103
+ end
104
+
105
+ describe 'no configged index name' do
106
+
107
+ it "gets a default name that makes sense for the index when not defined" do
108
+ index = Waistband::Index.new 'events_no_name', subs: %w(2013 01)
109
+ index.config_name.should eql 'events_no_name_test__2013_01'
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+
116
+ describe '#base_config_name' do
117
+
118
+ it "gets a default name that makes sense for the index when not defined" do
119
+ index = Waistband::Index.new 'events_no_name'
120
+ index.base_config_name.should eql 'events_no_name_test'
121
+ end
122
+
123
+ end
124
+
75
125
  end
@@ -5,8 +5,8 @@ describe Waistband::Query do
5
5
  let(:index) { Waistband::Index.new('search') }
6
6
  let(:geo_index) { Waistband::Index.new('geo') }
7
7
 
8
- let(:query) { Waistband::Query.new('search') }
9
- let(:geo_query) { Waistband::Query.new('geo') }
8
+ let(:query) { Waistband::Query.new(index) }
9
+ let(:geo_query) { Waistband::Query.new(geo_index) }
10
10
 
11
11
  let(:attrs) do
12
12
  {
data/spec/spec_helper.rb CHANGED
@@ -19,8 +19,10 @@ RSpec.configure do |config|
19
19
  end
20
20
  end
21
21
 
22
- config.before(:all) do |c|
23
- IndexHelper.prepare!
22
+ config.around(:each) do |example|
23
+ IndexHelper.create_all
24
+ example.run
25
+ IndexHelper.destroy_all
24
26
  end
25
27
 
26
28
  config.after(:each) do
@@ -4,21 +4,20 @@ class IndexHelper
4
4
 
5
5
  class << self
6
6
 
7
- def destroy_all!
7
+ def destroy_all
8
8
  IndexHelper::INDEXES.each do |index|
9
- Waistband::Index.new(index).destroy!
9
+ Waistband::Index.new(index).destroy
10
10
  end
11
+
12
+ Waistband::Index.new('events', subs: %w(2013 01)).destroy
11
13
  end
12
14
 
13
- def create_all!
15
+ def create_all
14
16
  IndexHelper::INDEXES.each do |index|
15
- Waistband::Index.new(index).create!
17
+ Waistband::Index.new(index).create
16
18
  end
17
- end
18
19
 
19
- def prepare!
20
- destroy_all!
21
- create_all!
20
+ Waistband::Index.new('events', subs: %w(2013 01)).create
22
21
  end
23
22
 
24
23
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: waistband
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.8.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-01-29 00:00:00.000000000 Z
12
+ date: 2014-02-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -118,6 +118,7 @@ files:
118
118
  - lib/waistband/version.rb
119
119
  - spec/config/waistband/waistband.yml
120
120
  - spec/config/waistband/waistband_events.yml
121
+ - spec/config/waistband/waistband_events_no_name.yml
121
122
  - spec/config/waistband/waistband_geo.yml
122
123
  - spec/config/waistband/waistband_search.yml
123
124
  - spec/lib/configuration_spec.rb
@@ -158,6 +159,7 @@ summary: Elastic Search all the things!
158
159
  test_files:
159
160
  - spec/config/waistband/waistband.yml
160
161
  - spec/config/waistband/waistband_events.yml
162
+ - spec/config/waistband/waistband_events_no_name.yml
161
163
  - spec/config/waistband/waistband_geo.yml
162
164
  - spec/config/waistband/waistband_search.yml
163
165
  - spec/lib/configuration_spec.rb