tire 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/tire/alias.rb ADDED
@@ -0,0 +1,296 @@
1
+ module Tire
2
+
3
+ # Represents an *alias* in _ElasticSearch_. An alias may point to one or multiple
4
+ # indices, for instance to separate physical indices into logical entities, where
5
+ # each user has a "virtual index" or for setting up "sliding window" scenarios.
6
+ #
7
+ # See: http://www.elasticsearch.org/guide/reference/api/admin-indices-aliases.html
8
+ #
9
+ class Alias
10
+
11
+ # Create an alias pointing to multiple indices:
12
+ #
13
+ # Tire::Alias.create name: 'my_alias', indices: ['index_1', 'index_2']
14
+ #
15
+ # Pass the routing and/or filtering configuration in the options Hash:
16
+ #
17
+ # a = Tire::Alias.new name: 'index_anne',
18
+ # indices: ['index_2012_04', 'index_2012_03', 'index_2012_02'],
19
+ # routing: 1,
20
+ # filter: { :terms => { :user => 'anne' } }
21
+ # a.save
22
+ #
23
+ # You may configure the alias in an imperative manner:
24
+ #
25
+ # a = Tire::Alias.new
26
+ # a.name('index_anne')
27
+ # a.index('index_2012_04')
28
+ # a.index('index_2012_03')
29
+ # # ...
30
+ # a.save
31
+ #
32
+ # or more declaratively, with a block:
33
+ #
34
+ # Tire::Alias.new name: 'my_alias' do
35
+ # index 'index_A'
36
+ # index 'index_B'
37
+ # filter :terms, username: 'mary'
38
+ # end
39
+ #
40
+ # To update an existing alias, find it by name, configure it and save it:
41
+ #
42
+ # a = Tire::Alias.find('my_alias')
43
+ # a.indices.delete 'index_A'
44
+ # a.indices.add 'index_B'
45
+ # a.indices.add 'index_C'
46
+ # a.save
47
+ #
48
+ # Or do it with a block:
49
+ #
50
+ # Tire::Alias.find('articles_aliased') do |a|
51
+ # a.indices.remove 'articles_2'
52
+ # puts '---', "#{a.name} says: /me as JSON >", a.as_json, '---'
53
+ # a.save
54
+ # end
55
+ #
56
+ # To remove indices from alias, you may want to use the `delete_all` method:
57
+ #
58
+ #
59
+ # require 'active_support/core_ext/numeric'
60
+ # require 'active_support/core_ext/date/calculations'
61
+ #
62
+ # a = Tire::Alias.find('articles_aliased')
63
+ # a.indices.delete_if do |i|
64
+ # Time.parse( i.gsub(/articles_/, '') ) < 4.weeks.ago rescue false
65
+ # end
66
+ # a.save
67
+ #
68
+ # To get all aliases, use the `Tire::Alias.all` method:
69
+ #
70
+ # Tire::Alias.all.each do |a|
71
+ # puts "#{a.name.rjust(30)} points to: #{a.indices}"
72
+ # end
73
+ #
74
+ def initialize(attributes={}, &block)
75
+ raise ArgumentError, "Please pass a Hash-like object" unless attributes.respond_to?(:each_pair)
76
+
77
+ @attributes = { :indices => IndexCollection.new([]) }
78
+
79
+ attributes.each_pair do |key, value|
80
+ if key.to_s =~ /index|indices/
81
+ @attributes[:indices] = IndexCollection.new(value)
82
+ else
83
+ @attributes[key.to_sym] = value
84
+ end
85
+ end
86
+
87
+ block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given?
88
+ end
89
+
90
+ # Returns a collection of Tire::Alias objects for all aliases defined in the cluster, or for a specific index.
91
+ #
92
+ def self.all(index=nil)
93
+ @response = Configuration.client.get [Configuration.url, index, '_aliases'].compact.join('/')
94
+
95
+ aliases = MultiJson.decode(@response.body).inject({}) do |result, (index, value)|
96
+ # 1] Skip indices without aliases
97
+ next result if value['aliases'].empty?
98
+
99
+ # 2] Build a reverse map of hashes (alias => indices, config)
100
+ value['aliases'].each do |key, value| (result[key] ||= { 'indices' => [] }).update(value)['indices'].push(index) end
101
+ result
102
+ end
103
+
104
+ # 3] Build a collection of Alias objects from hashes
105
+ aliases.map do |key, value|
106
+ self.new(value.update('name' => key))
107
+ end
108
+
109
+ ensure
110
+ # FIXME: Extract the `logged` method
111
+ Alias.new.logged '_aliases', %Q|curl "#{Configuration.url}/_aliases"|
112
+ end
113
+
114
+ # Returns an alias by name
115
+ #
116
+ def self.find(name, &block)
117
+ a = all.select { |a| a.name == name }.first
118
+ block.call(a) if block_given?
119
+ return a
120
+ end
121
+
122
+ # Create new alias
123
+ #
124
+ def self.create(attributes={}, &block)
125
+ new(attributes, &block).save
126
+ end
127
+
128
+ # Delegate to the `@attributes` Hash
129
+ #
130
+ def method_missing(method_name, *arguments)
131
+ @attributes.has_key?(method_name.to_sym) ? @attributes[method_name.to_sym] : super
132
+ end
133
+
134
+ # Get or set the alias name
135
+ #
136
+ def name(value=nil)
137
+ value ? (@attributes[:name] = value and return self) : @attributes[:name]
138
+ end
139
+
140
+ # Get or set the alias indices
141
+ #
142
+ def indices(*names)
143
+ names = Array(names).flatten
144
+ names.compact.empty? ? @attributes[:indices] : (names.each { |n| @attributes[:indices].push(n) } and return self)
145
+ end
146
+ alias_method :index, :indices
147
+
148
+ # Get or set the alias routing
149
+ #
150
+ def routing(value=nil)
151
+ value ? (@attributes[:routing] = value and return self) : @attributes[:routing]
152
+ end
153
+
154
+ # Get or set the alias routing
155
+ def filter(type=nil, *options)
156
+ type ? (@attributes[:filter] = Search::Filter.new(type, *options).to_hash and return self ) : @attributes[:filter]
157
+ end
158
+
159
+ # Save the alias in _ElasticSearch_
160
+ #
161
+ def save
162
+ @response = Configuration.client.post "#{Configuration.url}/_aliases", to_json
163
+
164
+ ensure
165
+ logged '_aliases', %Q|curl -X POST "#{Configuration.url}/_aliases" -d '#{to_json}'|
166
+ end
167
+
168
+ # Return a Hash suitable for JSON serialization
169
+ #
170
+ def as_json(options=nil)
171
+ actions = []
172
+ indices.add_indices.each do |index|
173
+ operation = { :index => index, :alias => name }
174
+ operation.update( { :routing => routing } ) if respond_to?(:routing) and routing
175
+ operation.update( { :filter => filter } ) if respond_to?(:filter) and filter
176
+ actions.push( { :add => operation } )
177
+ end
178
+
179
+ indices.remove_indices.each do |index|
180
+ operation = { :index => index, :alias => name }
181
+ actions.push( { :remove => operation } )
182
+ end
183
+
184
+ { :actions => actions }
185
+ end
186
+
187
+ # Return alias serialized in JSON for _ElasticSearch_
188
+ #
189
+ def to_json(options=nil)
190
+ as_json.to_json
191
+ end
192
+
193
+ def inspect
194
+ %Q|<#{self.class} #{@attributes.inspect}>|
195
+ end
196
+
197
+ def to_s
198
+ name
199
+ end
200
+
201
+ def logged(endpoint='/', curl='')
202
+ # FIXME: Extract the `logged` method into module and mix it into classes
203
+ if Configuration.logger
204
+ error = $!
205
+
206
+ Configuration.logger.log_request endpoint, @name, curl
207
+
208
+ code = @response ? @response.code : error.class rescue 200
209
+
210
+ if Configuration.logger.level.to_s == 'debug'
211
+ body = if @response
212
+ defined?(Yajl) ? Yajl::Encoder.encode(@response.body, :pretty => true) : MultiJson.encode(@response.body)
213
+ else
214
+ error.message rescue ''
215
+ end
216
+ else
217
+ body = ''
218
+ end
219
+
220
+ Configuration.logger.log_response code, nil, body
221
+ end
222
+ end
223
+
224
+ # Thin wrapper around array representing a collection of indices for a specific alias,
225
+ # which allows hooking into adding/removing indices.
226
+ #
227
+ # It keeps track of which aliases to add and which to remove in two separate collections,
228
+ # `add_indices` and `remove_indices`.
229
+ #
230
+ # It delegates Enumerable-like methods to the `add_indices` collection.
231
+ #
232
+ class IndexCollection
233
+ include Enumerable
234
+ attr_reader :add_indices, :remove_indices
235
+
236
+ def initialize(*values)
237
+ @add_indices = Array.new(values).flatten.compact
238
+ @remove_indices = []
239
+ end
240
+
241
+ def push(value)
242
+ @add_indices |= [value]
243
+ @remove_indices.delete value
244
+ end
245
+ alias_method :add, :push
246
+
247
+ def delete(value)
248
+ @add_indices.delete value
249
+ @remove_indices |= [value]
250
+ end
251
+ alias_method :remove, :delete
252
+
253
+ def delete_if(&block)
254
+ @add_indices.clone.each do |name|
255
+ delete(name) if block.call(name)
256
+ end
257
+ end
258
+
259
+ def each(&block)
260
+ @add_indices.each(&block)
261
+ end
262
+
263
+ def empty?
264
+ @add_indices.empty?
265
+ end
266
+
267
+ def clear
268
+ @remove_indices = @add_indices.clone
269
+ @add_indices.clear
270
+ end
271
+
272
+ def [](index)
273
+ @add_indices[index]
274
+ end
275
+
276
+ def size
277
+ @add_indices.size
278
+ end
279
+
280
+ def to_ary
281
+ @add_indices
282
+ end
283
+
284
+ def to_s
285
+ @add_indices.join(', ')
286
+ end
287
+
288
+ def inspect
289
+ %Q|<#{self.class} #{@add_indices.map{|i| "\"#{i}\""}.join(', ')}>|
290
+ end
291
+
292
+ end
293
+
294
+ end
295
+
296
+ end
data/lib/tire/dsl.rb CHANGED
@@ -36,13 +36,7 @@ module Tire
36
36
  end
37
37
 
38
38
  def aliases
39
- @response = Configuration.client.get "#{Configuration.url}/_aliases"
40
- MultiJson.decode(@response.body).inject({}) do |acc, (index, value)|
41
- next acc if value['aliases'].empty?
42
-
43
- acc[index] = value['aliases'].keys
44
- acc
45
- end
39
+ Alias.all
46
40
  end
47
41
 
48
42
  end
@@ -36,8 +36,7 @@ module Tire
36
36
 
37
37
  def self.put(url, data)
38
38
  @client.url = url
39
- @client.put_data = data
40
- @client.http_put
39
+ @client.http_put data
41
40
  Response.new @client.body_str, @client.response_code
42
41
  end
43
42
 
data/lib/tire/index.rb CHANGED
@@ -5,7 +5,7 @@ module Tire
5
5
 
6
6
  def initialize(name, &block)
7
7
  @name = name
8
- instance_eval(&block) if block_given?
8
+ block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given?
9
9
  end
10
10
 
11
11
  def url
@@ -41,36 +41,15 @@ module Tire
41
41
  end
42
42
 
43
43
  def add_alias(alias_name, configuration={})
44
- payload = {'actions' => [ {'add' => {'index' => @name, 'alias' => alias_name}.merge(configuration) } ]}
45
- @response = Configuration.client.post "#{Configuration.url}/_aliases", MultiJson.encode(payload)
46
- @response.success?
47
-
48
- ensure
49
- curl = %Q|curl -X POST "#{Configuration.url}/_aliases" -d '#{MultiJson.encode(payload)}'|
50
- logged('POST', curl)
44
+ Alias.create(configuration.merge( :name => alias_name, :index => @name ) )
51
45
  end
52
46
 
53
47
  def remove_alias(alias_name)
54
- payload = {'actions' => [{'remove' => {'index' => @name, 'alias' => alias_name}}]}
55
- @response = Configuration.client.post "#{Configuration.url}/_aliases", MultiJson.encode(payload)
56
- @response.success?
57
-
58
- ensure
59
- curl = %Q|curl -X POST "#{Configuration.url}/_aliases" -d '#{MultiJson.encode(payload)}'|
60
- logged('POST', curl)
48
+ Alias.find(alias_name) { |a| a.indices.delete @name }.save
61
49
  end
62
50
 
63
- def aliases(alias_name = nil)
64
- @response = Configuration.client.get "#{url}/_aliases"
65
- if alias_name
66
- MultiJson.decode(@response.body)[@name]['aliases'][alias_name]
67
- else
68
- MultiJson.decode(@response.body)[@name]['aliases'].keys
69
- end
70
-
71
- ensure
72
- curl = %Q|curl "#{url}/_aliases?pretty"|
73
- logged('GET', curl)
51
+ def aliases(alias_name=nil)
52
+ alias_name ? Alias.all(@name).select { |a| a.name == alias_name }.first : Alias.all(@name)
74
53
  end
75
54
 
76
55
  def mapping
data/lib/tire/version.rb CHANGED
@@ -1,14 +1,22 @@
1
1
  module Tire
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.2"
3
3
 
4
4
  CHANGELOG =<<-END
5
5
  IMPORTANT CHANGES LATELY:
6
6
 
7
+ Version 0.4.1
8
+ -------------
7
9
  * Added a Index#settings method to retrieve index settings as a Hash
8
10
  * Added support for the "scan" search in the Ruby API
9
11
  * Added support for reindexing the index documents into new index
10
12
  * Added basic support for index aliases
11
13
  * Changed, that Index#bulk_store runs against an index endpoint, not against `/_bulk`
12
14
  * Refactorings, fixes, Ruby 1.8 compatibility
15
+
16
+ Version 0.4.2
17
+ -------------
18
+ * Fixed incorrect handling of PUT requests in the Curb client
19
+ * Fixed, that blocks passed to `Tire::Index.new` or `Tire.index` losed the scope
20
+ * Added `Tire::Alias`, interface and DSL to manage aliases as resources
13
21
  END
14
22
  end
data/lib/tire.rb CHANGED
@@ -29,6 +29,7 @@ require 'tire/results/pagination'
29
29
  require 'tire/results/collection'
30
30
  require 'tire/results/item'
31
31
  require 'tire/index'
32
+ require 'tire/alias'
32
33
  require 'tire/dsl'
33
34
  require 'tire/model/naming'
34
35
  require 'tire/model/callbacks'
@@ -1,5 +1,8 @@
1
1
  require 'test_helper'
2
2
 
3
+ require 'active_support/core_ext/numeric'
4
+ require 'active_support/core_ext/date/calculations'
5
+
3
6
  module Tire
4
7
 
5
8
  class IndexAliasesIntegrationTest < Test::Unit::TestCase
@@ -50,17 +53,68 @@ module Tire
50
53
  should "retrieve a list of aliases for an index" do
51
54
  @index.add_alias 'index-aliased'
52
55
 
53
- assert_equal ['index-aliased'], @index.aliases
56
+ assert_equal ['index-aliased'], @index.aliases.map(&:name)
54
57
  end
55
58
 
56
59
  should "retrieve the properties of an alias" do
57
60
  @index.add_alias 'index-aliased', :routing => '1'
58
61
 
59
- assert_equal(
60
- { 'index_routing' => '1',
61
- 'search_routing' => '1' },
62
- @index.aliases('index-aliased') )
62
+ assert_equal '1', @index.aliases('index-aliased').search_routing
63
+ end
64
+ end
65
+
66
+ context "In the 'sliding window' scenario" do
67
+
68
+ setup do
69
+ WINDOW_SIZE_IN_WEEKS = 4
70
+
71
+ @indices = WINDOW_SIZE_IN_WEEKS.times.map { |number| "articles_#{number.weeks.ago.strftime('%Y-%m-%d')}" }
72
+
73
+ @indices.each_with_index do |name,i|
74
+ Tire.index(name) do
75
+ delete
76
+ create
77
+ store :title => "Document #{i}"
78
+ refresh
79
+ end
80
+ Alias.new(:name => "articles_current") { |a| a.indices(name) and a.save }
81
+ end
82
+ end
83
+
84
+ teardown do
85
+ @indices.each { |index| Tire.index(index).delete }
63
86
  end
87
+
88
+ should "add a new index to alias" do
89
+ @indices << "articles_#{(WINDOW_SIZE_IN_WEEKS+1).weeks.ago.strftime('%Y-%m-%d')}"
90
+ Tire.index(@indices.last).create
91
+ Alias.new(:name => "articles_current") { |a| a.index @indices.last and a.save }
92
+
93
+ a = Alias.find("articles_current")
94
+ assert_equal 5, a.indices.size
95
+ end
96
+
97
+ should "remove the stale index from the alias" do
98
+ Alias.find("articles_current") do |a|
99
+ # Remove all indices older then 2 weeks from the alias
100
+ a.indices.delete_if do |i|
101
+ Time.parse( i.gsub(/articles_/, '') ) < 2.weeks.ago rescue false
102
+ end
103
+ a.save
104
+ end
105
+
106
+ assert_equal 2, Alias.find("articles_current").indices.size
107
+ end
108
+
109
+ should "search within the alias" do
110
+ Alias.find("articles_current") do |a|
111
+ a.indices.clear and a.indices @indices[0..1] and a.save
112
+ end
113
+
114
+ assert_equal 4, Tire.search(@indices) { query {all} }.results.size
115
+ assert_equal 2, Tire.search("articles_current") { query {all} }.results.size
116
+ end
117
+
64
118
  end
65
119
 
66
120
  end
@@ -0,0 +1,275 @@
1
+ require 'test_helper'
2
+
3
+ module Tire
4
+
5
+ class IndexAliasTest < Test::Unit::TestCase
6
+
7
+ context "Index Alias" do
8
+ teardown { Configuration.reset }
9
+
10
+ context "initialization" do
11
+
12
+ should "have a name and defaults" do
13
+ @alias = Alias.new :name => 'dummy'
14
+ assert_equal 'dummy', @alias.name
15
+ assert @alias.indices.empty?
16
+ end
17
+
18
+ should "have indices" do
19
+ @alias = Alias.new :indices => ['index_A', 'index_B']
20
+ assert_equal ['index_A', 'index_B'], @alias.indices.to_a
21
+ end
22
+
23
+ should "have single index" do
24
+ @alias = Alias.new :index => 'index_A'
25
+ assert_equal ['index_A'], @alias.indices.to_a
26
+ assert_equal ['index_A'], @alias.index.to_a
27
+ end
28
+
29
+ should "have properties" do
30
+ @alias = Alias.new :routing => '1'
31
+ assert_equal '1', @alias.routing
32
+ end
33
+
34
+ should "allow to add indices in a block" do
35
+ @alias = Alias.new do
36
+ index 'index_A'
37
+ index 'index_B'
38
+ end
39
+ assert_equal ['index_A', 'index_B'], @alias.indices.to_a
40
+
41
+ @alias = Alias.new do |a|
42
+ a.index 'index_A'
43
+ a.index 'index_B'
44
+ end
45
+ assert_equal ['index_A', 'index_B'], @alias.indices.to_a
46
+ end
47
+
48
+ should "allow to define filter with DSL" do
49
+ @alias = Alias.new do
50
+ filter :terms, :username => 'mary'
51
+ end
52
+
53
+ assert_equal( { :terms => { :username => 'mary' } }, @alias.filter )
54
+ end
55
+
56
+ should "allow chaining" do
57
+ @alias = Alias.new.name('my_alias').index('index_1').index('index_2').routing('1')
58
+
59
+ assert_equal 'my_alias', @alias.name
60
+ assert_equal ['index_1', 'index_2'], @alias.indices.to_a
61
+ assert_equal '1', @alias.routing
62
+ end
63
+
64
+ should "be converted to JSON" do
65
+ @alias = Alias.new :name => 'index_anne',
66
+ :indices => ['index_2012_04', 'index_2012_03', 'index_2012_02'],
67
+ :routing => 1,
68
+ :filter => { :terms => { :user => 'anne' } }
69
+ # p @alias.to_json
70
+ result = MultiJson.decode @alias.to_json
71
+
72
+ assert_equal 3, result['actions'].size
73
+ assert_equal 'index_2012_04', result['actions'][0]['add']['index']
74
+ end
75
+
76
+ should "be converted to string" do
77
+ @alias = Alias.new :name => 'my_alias'
78
+ assert_equal 'my_alias', @alias.to_s
79
+ end
80
+
81
+ end
82
+
83
+ context "updating" do
84
+ setup do
85
+ Configuration.client.expects(:get).
86
+ returns( mock_response( %q|{"index_A":{"aliases":{}},"index_B":{"aliases":{"alias_john":{"filter":{"term":{"user":"john"}}},"alias_martha":{"filter":{"term":{"user":"martha"}}}}},"mongoid_articles":{"aliases":{}},"index_C":{"aliases":{"alias_martha":{"filter":{"term":{"user":"martha"}}}}}}|), 200 ).at_least_once
87
+ end
88
+
89
+ should "add indices to alias" do
90
+ Configuration.client.expects(:post).with do |url, json|
91
+ # puts json
92
+ MultiJson.decode(json)['actions'].any? do |a|
93
+ a['add']['index'] == 'index_A' &&
94
+ a['add']['alias'] == 'alias_martha'
95
+ end
96
+ end.returns(mock_response('{}'), 200)
97
+
98
+ a = Alias.find('alias_martha')
99
+ a.indices.push 'index_A'
100
+ a.save
101
+ end
102
+
103
+ should "remove indices from alias" do
104
+ Configuration.client.expects(:post).with do |url, json|
105
+ # puts json
106
+ MultiJson.decode(json)['actions'].any? do |a|
107
+ a['remove'] &&
108
+ a['remove']['index'] == 'index_A' &&
109
+ a['remove']['alias'] == 'alias_martha'
110
+ end
111
+ end.returns(mock_response('{}'), 200)
112
+
113
+ a = Alias.find('alias_martha')
114
+ a.indices.delete 'index_A'
115
+ a.save
116
+ end
117
+
118
+ should "change alias configuration" do
119
+ Configuration.client.expects(:post).with do |url, json|
120
+ # puts json
121
+ MultiJson.decode(json)['actions'].all? { |a| a['add']['routing'] == 'martha' }
122
+ end.returns(mock_response('{}'), 200)
123
+
124
+ a = Alias.find('alias_martha')
125
+ a.routing('martha')
126
+ a.save
127
+ end
128
+
129
+ end
130
+
131
+ context "saving" do
132
+
133
+ should "send data to ElasticSearch" do
134
+ Configuration.client.expects(:post).with do |url, json|
135
+ url == "#{Configuration.url}/_aliases" &&
136
+ json =~ /"index":"index_2012_05"/ &&
137
+ json =~ /"alias":"index_current"/
138
+ end.returns(mock_response('{}'), 200)
139
+
140
+ @alias = Alias.new :name => 'index_current', :index => 'index_2012_05'
141
+ @alias.save
142
+ end
143
+
144
+ should "log request" do
145
+ Tire.configure { logger '/dev/null' }
146
+
147
+ Configuration.client.expects(:post)
148
+ Configuration.logger.expects(:log_request)
149
+ Alias.new( :name => 'index_current', :index => 'index_2012_05' ).save
150
+ end
151
+
152
+ end
153
+
154
+ context "finding" do
155
+ setup do
156
+ Configuration.client.expects(:get).with do |url, json|
157
+ url == "#{Configuration.url}/_aliases"
158
+ end.returns( mock_response( %q|{"index_A":{"aliases":{}},"index_B":{"aliases":{"alias_john":{"filter":{"term":{"user":"john"}}},"alias_martha":{"filter":{"term":{"user":"martha"}}}}},"index_C":{"aliases":{"alias_martha":{"filter":{"term":{"user":"martha"}}}}}}|), 200 )
159
+ end
160
+
161
+ should "find all aliases" do
162
+ aliases = Alias.all
163
+ # p aliases
164
+ assert_equal 2, aliases.size
165
+ assert_equal ['index_B', 'index_C'], aliases.select { |a| a.name == 'alias_martha'}.first.indices.to_a.sort
166
+ end
167
+
168
+ should "find aliases for a specific index" do
169
+ Configuration.client.unstub(:get)
170
+ Configuration.client.expects(:get).with do |url, json|
171
+ url == "#{Configuration.url}/index_C/_aliases"
172
+ end.returns( mock_response( %q|{"index_C":{"aliases":{"alias_martha":{"filter":{"term":{"user":"martha"}}}}}}|), 200 )
173
+
174
+ aliases = Alias.all('index_C')
175
+ # p aliases
176
+ assert_equal 1, aliases.size
177
+ assert_equal ['index_C'], aliases.last.indices.to_a
178
+ end
179
+
180
+ should "find an alias" do
181
+ a = Alias.find('alias_martha')
182
+ assert_instance_of Alias, a
183
+ assert_equal ['index_B', 'index_C'], a.indices.to_a.sort
184
+ end
185
+
186
+ should "find an alias and configure it with a block" do
187
+ a = Alias.find('alias_martha') do |a|
188
+ a.indices.delete 'index_A'
189
+ a.indices.add 'index_D'
190
+ end
191
+
192
+ assert_equal ['index_B', 'index_C', 'index_D'], a.indices.to_a.sort
193
+ end
194
+
195
+ end
196
+
197
+ context "creating" do
198
+ setup do
199
+ Configuration.client.expects(:post).with do |url, json|
200
+ url == "#{Configuration.url}/_aliases" &&
201
+ json =~ /"index":"index_2012_05"/ &&
202
+ json =~ /"alias":"index_current"/
203
+ end.returns(mock_response('{}'), 200)
204
+ end
205
+
206
+ should "create the alias" do
207
+ Alias.create :name => 'index_current', :index => 'index_2012_05'
208
+ end
209
+
210
+ should "create the alias with a block" do
211
+ Alias.create :name => 'index_current' do
212
+ index 'index_2012_05'
213
+ end
214
+ end
215
+
216
+ end
217
+
218
+ end
219
+
220
+ context "IndexCollection" do
221
+
222
+ should "be intialized with an array or arguments list" do
223
+ c1 = Alias::IndexCollection.new ['1', '2', '3']
224
+ c2 = Alias::IndexCollection.new '1', '2', '3'
225
+ assert_equal c1.to_a, c2.to_a
226
+ end
227
+
228
+ should "be iterable" do
229
+ c = Alias::IndexCollection.new '1', '2', '3'
230
+ assert_respond_to c, :each
231
+ assert_respond_to c, :size
232
+ assert_equal [1, 2, 3], c.map(&:to_i)
233
+ end
234
+
235
+ should "allow adding values" do
236
+ c = Alias::IndexCollection.new '1', '2'
237
+ c.add '3'
238
+ assert_equal 3, c.size
239
+ assert_equal ['1', '2', '3'], c.add_indices
240
+ assert_equal [], c.remove_indices
241
+ end
242
+
243
+ should "allow removing values" do
244
+ c = Alias::IndexCollection.new '1', '2'
245
+ c.remove '1'
246
+ assert_equal 1, c.size
247
+ assert_equal ['2'], c.add_indices
248
+ assert_equal ['1'], c.remove_indices
249
+ end
250
+
251
+ should "clear everything" do
252
+ c = Alias::IndexCollection.new '1', '2'
253
+ c.clear
254
+ assert_equal 0, c.size
255
+ assert_equal [], c.add_indices
256
+ assert_equal ['1', '2'], c.remove_indices
257
+ end
258
+
259
+ should "respond to empty" do
260
+ c = Alias::IndexCollection.new
261
+ assert c.empty?, "#{c.inspect} should be empty"
262
+ end
263
+
264
+ should "remove values with a block" do
265
+ c = Alias::IndexCollection.new '1', '2', '3'
266
+
267
+ c.delete_if { |a| a.to_i > 1 }
268
+ assert_equal 1, c.size
269
+ assert_equal ['1'], c.add_indices
270
+ assert_equal ['2', '3'], c.remove_indices
271
+ end
272
+
273
+ end
274
+ end
275
+ end
@@ -75,6 +75,7 @@ module Tire
75
75
  end
76
76
 
77
77
  should "delete an index alias" do
78
+ Configuration.client.expects(:get).returns(mock_response({'dummy' => {'aliases' => {'foo' => {}}}}.to_json))
78
79
  Configuration.client.expects(:post).with do |url, payload|
79
80
  url =~ /_aliases/ &&
80
81
  MultiJson.decode(payload)['actions'][0]['remove'] == {'index' => 'dummy', 'alias' => 'foo'}
@@ -87,14 +88,14 @@ module Tire
87
88
  json = {'dummy' => {'aliases' => {'foo' => {}}}}.to_json
88
89
  Configuration.client.expects(:get).returns(mock_response(json))
89
90
 
90
- assert_equal ['foo'], @index.aliases
91
+ assert_equal ['foo'], @index.aliases.map(&:name)
91
92
  end
92
93
 
93
94
  should "return properties of an alias" do
94
95
  json = {'dummy' => { 'aliases' => {'foo' => { 'filter' => { 'term' => { 'user' => 'john' } }}} }}.to_json
95
96
  Configuration.client.expects(:get).returns(mock_response(json))
96
97
 
97
- assert_equal( { 'filter' => { 'term' => {'user' => 'john'} } }, @index.aliases('foo') )
98
+ assert_equal( { 'term' => {'user' => 'john'} }, @index.aliases('foo').filter )
98
99
  end
99
100
 
100
101
  should "refresh the index" do
@@ -466,7 +467,7 @@ module Tire
466
467
  context "namespaced models" do
467
468
  should "not URL-escape the document_type" do
468
469
  Configuration.client.expects(:post).with do |url, json|
469
- puts url, json
470
+ # puts url, json
470
471
  url == "#{Configuration.url}/my_namespace_my_models/_bulk" &&
471
472
  json =~ %r|"_index":"my_namespace_my_models"| &&
472
473
  json =~ %r|"_type":"my_namespace/my_model"|
@@ -813,6 +814,26 @@ module Tire
813
814
  end
814
815
 
815
816
  end
817
+
818
+ context "when accessing the variables from outer scope" do
819
+
820
+ should "access the variables" do
821
+ @my_title = 'Title From Outer Space'
822
+
823
+ def index_something
824
+ @tags = ['block', 'scope', 'revenge']
825
+
826
+ Index.any_instance.expects(:store).with(title: 'Title From Outer Space', tags: ['block', 'scope', 'revenge'])
827
+
828
+ Tire::Index.new 'outer-space' do |index|
829
+ index.store title: @my_title, tags: @tags
830
+ end
831
+ end
832
+
833
+ index_something
834
+ end
835
+
836
+ end
816
837
  end
817
838
 
818
839
  end
@@ -96,7 +96,10 @@ module Tire
96
96
  json = {'dummy' => {'aliases' => {'foo' => {}}}}.to_json
97
97
  Configuration.client.expects(:get).returns(mock_response(json))
98
98
 
99
- assert_equal({'dummy' => ['foo']}, Tire.aliases)
99
+ aliases = Tire.aliases
100
+
101
+ assert_equal 'foo', aliases.first.name
102
+ assert_equal ['dummy'], aliases.first.indices.to_a
100
103
  end
101
104
 
102
105
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tire
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-03 00:00:00.000000000 Z
12
+ date: 2012-05-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70192999444940 !ruby/object:Gem::Requirement
16
+ requirement: &70096833142860 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70192999444940
24
+ version_requirements: *70096833142860
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rest-client
27
- requirement: &70192999442960 !ruby/object:Gem::Requirement
27
+ requirement: &70096833142140 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '1.6'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70192999442960
35
+ version_requirements: *70096833142140
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: multi_json
38
- requirement: &70192999441940 !ruby/object:Gem::Requirement
38
+ requirement: &70096833140840 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '1.0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70192999441940
46
+ version_requirements: *70096833140840
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: activemodel
49
- requirement: &70192999441400 !ruby/object:Gem::Requirement
49
+ requirement: &70096833140320 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '3.0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70192999441400
57
+ version_requirements: *70096833140320
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: hashr
60
- requirement: &70192999440740 !ruby/object:Gem::Requirement
60
+ requirement: &70096833139860 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 0.0.19
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *70192999440740
68
+ version_requirements: *70096833139860
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
- requirement: &70192999440040 !ruby/object:Gem::Requirement
71
+ requirement: &70096833139320 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '1.0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70192999440040
79
+ version_requirements: *70096833139320
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: yajl-ruby
82
- requirement: &70192999439360 !ruby/object:Gem::Requirement
82
+ requirement: &70096833138860 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ~>
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '1.0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70192999439360
90
+ version_requirements: *70096833138860
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: shoulda
93
- requirement: &70192999438620 !ruby/object:Gem::Requirement
93
+ requirement: &70096833138480 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: '0'
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *70192999438620
101
+ version_requirements: *70096833138480
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: mocha
104
- requirement: &70192999438160 !ruby/object:Gem::Requirement
104
+ requirement: &70096833137960 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ! '>='
@@ -109,10 +109,10 @@ dependencies:
109
109
  version: '0'
110
110
  type: :development
111
111
  prerelease: false
112
- version_requirements: *70192999438160
112
+ version_requirements: *70096833137960
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: activerecord
115
- requirement: &70192999437460 !ruby/object:Gem::Requirement
115
+ requirement: &70096833137400 !ruby/object:Gem::Requirement
116
116
  none: false
117
117
  requirements:
118
118
  - - ! '>='
@@ -120,10 +120,10 @@ dependencies:
120
120
  version: '3.0'
121
121
  type: :development
122
122
  prerelease: false
123
- version_requirements: *70192999437460
123
+ version_requirements: *70096833137400
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: sqlite3
126
- requirement: &70192999436980 !ruby/object:Gem::Requirement
126
+ requirement: &70096833136840 !ruby/object:Gem::Requirement
127
127
  none: false
128
128
  requirements:
129
129
  - - ! '>='
@@ -131,10 +131,10 @@ dependencies:
131
131
  version: '0'
132
132
  type: :development
133
133
  prerelease: false
134
- version_requirements: *70192999436980
134
+ version_requirements: *70096833136840
135
135
  - !ruby/object:Gem::Dependency
136
136
  name: mongoid
137
- requirement: &70192999436360 !ruby/object:Gem::Requirement
137
+ requirement: &70096833131700 !ruby/object:Gem::Requirement
138
138
  none: false
139
139
  requirements:
140
140
  - - ~>
@@ -142,10 +142,10 @@ dependencies:
142
142
  version: '2.2'
143
143
  type: :development
144
144
  prerelease: false
145
- version_requirements: *70192999436360
145
+ version_requirements: *70096833131700
146
146
  - !ruby/object:Gem::Dependency
147
147
  name: bson_ext
148
- requirement: &70192999435800 !ruby/object:Gem::Requirement
148
+ requirement: &70096833131100 !ruby/object:Gem::Requirement
149
149
  none: false
150
150
  requirements:
151
151
  - - ! '>='
@@ -153,10 +153,10 @@ dependencies:
153
153
  version: '0'
154
154
  type: :development
155
155
  prerelease: false
156
- version_requirements: *70192999435800
156
+ version_requirements: *70096833131100
157
157
  - !ruby/object:Gem::Dependency
158
158
  name: redis-persistence
159
- requirement: &70192999435140 !ruby/object:Gem::Requirement
159
+ requirement: &70096833130080 !ruby/object:Gem::Requirement
160
160
  none: false
161
161
  requirements:
162
162
  - - ! '>='
@@ -164,10 +164,10 @@ dependencies:
164
164
  version: '0'
165
165
  type: :development
166
166
  prerelease: false
167
- version_requirements: *70192999435140
167
+ version_requirements: *70096833130080
168
168
  - !ruby/object:Gem::Dependency
169
169
  name: curb
170
- requirement: &70192999434360 !ruby/object:Gem::Requirement
170
+ requirement: &70096833129280 !ruby/object:Gem::Requirement
171
171
  none: false
172
172
  requirements:
173
173
  - - ! '>='
@@ -175,10 +175,10 @@ dependencies:
175
175
  version: '0'
176
176
  type: :development
177
177
  prerelease: false
178
- version_requirements: *70192999434360
178
+ version_requirements: *70096833129280
179
179
  - !ruby/object:Gem::Dependency
180
180
  name: minitest
181
- requirement: &70192999423380 !ruby/object:Gem::Requirement
181
+ requirement: &70096833128320 !ruby/object:Gem::Requirement
182
182
  none: false
183
183
  requirements:
184
184
  - - ! '>='
@@ -186,10 +186,10 @@ dependencies:
186
186
  version: '0'
187
187
  type: :development
188
188
  prerelease: false
189
- version_requirements: *70192999423380
189
+ version_requirements: *70096833128320
190
190
  - !ruby/object:Gem::Dependency
191
191
  name: rdoc
192
- requirement: &70192999422820 !ruby/object:Gem::Requirement
192
+ requirement: &70096833127440 !ruby/object:Gem::Requirement
193
193
  none: false
194
194
  requirements:
195
195
  - - ! '>='
@@ -197,10 +197,10 @@ dependencies:
197
197
  version: '0'
198
198
  type: :development
199
199
  prerelease: false
200
- version_requirements: *70192999422820
200
+ version_requirements: *70096833127440
201
201
  - !ruby/object:Gem::Dependency
202
202
  name: turn
203
- requirement: &70192999422100 !ruby/object:Gem::Requirement
203
+ requirement: &70096833125920 !ruby/object:Gem::Requirement
204
204
  none: false
205
205
  requirements:
206
206
  - - ~>
@@ -208,7 +208,7 @@ dependencies:
208
208
  version: '0.9'
209
209
  type: :development
210
210
  prerelease: false
211
- version_requirements: *70192999422100
211
+ version_requirements: *70096833125920
212
212
  description: ! " Tire is a Ruby client for the ElasticSearch search engine/database.\n\n
213
213
  \ It provides Ruby-like API for fluent communication with the ElasticSearch server\n
214
214
  \ and blends with ActiveModel class for convenient usage in Rails applications.\n\n
@@ -233,6 +233,7 @@ files:
233
233
  - examples/rails-application-template.rb
234
234
  - examples/tire-dsl.rb
235
235
  - lib/tire.rb
236
+ - lib/tire/alias.rb
236
237
  - lib/tire/configuration.rb
237
238
  - lib/tire/dsl.rb
238
239
  - lib/tire/http/client.rb
@@ -317,6 +318,7 @@ files:
317
318
  - test/unit/configuration_test.rb
318
319
  - test/unit/http_client_test.rb
319
320
  - test/unit/http_response_test.rb
321
+ - test/unit/index_alias_test.rb
320
322
  - test/unit/index_test.rb
321
323
  - test/unit/logger_test.rb
322
324
  - test/unit/model_callbacks_test.rb
@@ -339,12 +341,15 @@ homepage: http://github.com/karmi/tire
339
341
  licenses: []
340
342
  post_install_message: ! "================================================================================\n\n
341
343
  \ Please check the documentation at <http://karmi.github.com/tire/>.\n\n--------------------------------------------------------------------------------\n\n
342
- \ IMPORTANT CHANGES LATELY:\n\n * Added a Index#settings method to retrieve index
343
- settings as a Hash\n * Added support for the \"scan\" search in the Ruby API\n
344
- \ * Added support for reindexing the index documents into new index\n * Added basic
345
- support for index aliases\n * Changed, that Index#bulk_store runs against an index
346
- endpoint, not against `/_bulk`\n * Refactorings, fixes, Ruby 1.8 compatibility\n\n
347
- \ See the full changelog at <http://github.com/karmi/tire/commits/v0.4.1>.\n\n--------------------------------------------------------------------------------\n"
344
+ \ IMPORTANT CHANGES LATELY:\n\n Version 0.4.1\n -------------\n * Added a Index#settings
345
+ method to retrieve index settings as a Hash\n * Added support for the \"scan\"
346
+ search in the Ruby API\n * Added support for reindexing the index documents into
347
+ new index\n * Added basic support for index aliases\n * Changed, that Index#bulk_store
348
+ runs against an index endpoint, not against `/_bulk`\n * Refactorings, fixes, Ruby
349
+ 1.8 compatibility\n\n Version 0.4.2\n -------------\n * Fixed incorrect handling
350
+ of PUT requests in the Curb client\n * Fixed, that blocks passed to `Tire::Index.new`
351
+ or `Tire.index` losed the scope\n * Added `Tire::Alias`, interface and DSL to manage
352
+ aliases as resources\n\n See the full changelog at <http://github.com/karmi/tire/commits/v0.4.2>.\n\n--------------------------------------------------------------------------------\n"
348
353
  rdoc_options:
349
354
  - --charset=UTF-8
350
355
  require_paths:
@@ -419,6 +424,7 @@ test_files:
419
424
  - test/unit/configuration_test.rb
420
425
  - test/unit/http_client_test.rb
421
426
  - test/unit/http_response_test.rb
427
+ - test/unit/index_alias_test.rb
422
428
  - test/unit/index_test.rb
423
429
  - test/unit/logger_test.rb
424
430
  - test/unit/model_callbacks_test.rb