tire 0.4.1 → 0.4.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/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