load_balanced_tire 0.1
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/.gitignore +14 -0
- data/.travis.yml +29 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +760 -0
- data/Rakefile +78 -0
- data/examples/rails-application-template.rb +249 -0
- data/examples/tire-dsl.rb +876 -0
- data/lib/tire.rb +55 -0
- data/lib/tire/alias.rb +296 -0
- data/lib/tire/configuration.rb +30 -0
- data/lib/tire/dsl.rb +43 -0
- data/lib/tire/http/client.rb +62 -0
- data/lib/tire/http/clients/curb.rb +61 -0
- data/lib/tire/http/clients/faraday.rb +71 -0
- data/lib/tire/http/response.rb +27 -0
- data/lib/tire/index.rb +361 -0
- data/lib/tire/logger.rb +60 -0
- data/lib/tire/model/callbacks.rb +40 -0
- data/lib/tire/model/import.rb +26 -0
- data/lib/tire/model/indexing.rb +128 -0
- data/lib/tire/model/naming.rb +100 -0
- data/lib/tire/model/percolate.rb +99 -0
- data/lib/tire/model/persistence.rb +71 -0
- data/lib/tire/model/persistence/attributes.rb +143 -0
- data/lib/tire/model/persistence/finders.rb +66 -0
- data/lib/tire/model/persistence/storage.rb +69 -0
- data/lib/tire/model/search.rb +307 -0
- data/lib/tire/results/collection.rb +114 -0
- data/lib/tire/results/item.rb +86 -0
- data/lib/tire/results/pagination.rb +54 -0
- data/lib/tire/rubyext/hash.rb +8 -0
- data/lib/tire/rubyext/ruby_1_8.rb +7 -0
- data/lib/tire/rubyext/symbol.rb +11 -0
- data/lib/tire/search.rb +188 -0
- data/lib/tire/search/facet.rb +74 -0
- data/lib/tire/search/filter.rb +28 -0
- data/lib/tire/search/highlight.rb +37 -0
- data/lib/tire/search/query.rb +186 -0
- data/lib/tire/search/scan.rb +114 -0
- data/lib/tire/search/script_field.rb +23 -0
- data/lib/tire/search/sort.rb +25 -0
- data/lib/tire/tasks.rb +135 -0
- data/lib/tire/utils.rb +17 -0
- data/lib/tire/version.rb +22 -0
- data/test/fixtures/articles/1.json +1 -0
- data/test/fixtures/articles/2.json +1 -0
- data/test/fixtures/articles/3.json +1 -0
- data/test/fixtures/articles/4.json +1 -0
- data/test/fixtures/articles/5.json +1 -0
- data/test/integration/active_model_indexing_test.rb +51 -0
- data/test/integration/active_model_searchable_test.rb +114 -0
- data/test/integration/active_record_searchable_test.rb +446 -0
- data/test/integration/boolean_queries_test.rb +43 -0
- data/test/integration/count_test.rb +34 -0
- data/test/integration/custom_score_queries_test.rb +88 -0
- data/test/integration/dis_max_queries_test.rb +68 -0
- data/test/integration/dsl_search_test.rb +22 -0
- data/test/integration/explanation_test.rb +44 -0
- data/test/integration/facets_test.rb +259 -0
- data/test/integration/filtered_queries_test.rb +66 -0
- data/test/integration/filters_test.rb +63 -0
- data/test/integration/fuzzy_queries_test.rb +20 -0
- data/test/integration/highlight_test.rb +64 -0
- data/test/integration/index_aliases_test.rb +122 -0
- data/test/integration/index_mapping_test.rb +43 -0
- data/test/integration/index_store_test.rb +96 -0
- data/test/integration/index_update_document_test.rb +111 -0
- data/test/integration/mongoid_searchable_test.rb +309 -0
- data/test/integration/percolator_test.rb +111 -0
- data/test/integration/persistent_model_test.rb +130 -0
- data/test/integration/prefix_query_test.rb +43 -0
- data/test/integration/query_return_version_test.rb +70 -0
- data/test/integration/query_string_test.rb +52 -0
- data/test/integration/range_queries_test.rb +36 -0
- data/test/integration/reindex_test.rb +46 -0
- data/test/integration/results_test.rb +39 -0
- data/test/integration/scan_test.rb +56 -0
- data/test/integration/script_fields_test.rb +38 -0
- data/test/integration/sort_test.rb +36 -0
- data/test/integration/text_query_test.rb +39 -0
- data/test/models/active_model_article.rb +31 -0
- data/test/models/active_model_article_with_callbacks.rb +49 -0
- data/test/models/active_model_article_with_custom_document_type.rb +7 -0
- data/test/models/active_model_article_with_custom_index_name.rb +7 -0
- data/test/models/active_record_models.rb +122 -0
- data/test/models/article.rb +15 -0
- data/test/models/mongoid_models.rb +97 -0
- data/test/models/persistent_article.rb +11 -0
- data/test/models/persistent_article_in_namespace.rb +12 -0
- data/test/models/persistent_article_with_casting.rb +28 -0
- data/test/models/persistent_article_with_defaults.rb +11 -0
- data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
- data/test/models/supermodel_article.rb +17 -0
- data/test/models/validated_model.rb +11 -0
- data/test/test_helper.rb +93 -0
- data/test/unit/active_model_lint_test.rb +17 -0
- data/test/unit/configuration_test.rb +74 -0
- data/test/unit/http_client_test.rb +76 -0
- data/test/unit/http_response_test.rb +49 -0
- data/test/unit/index_alias_test.rb +275 -0
- data/test/unit/index_test.rb +894 -0
- data/test/unit/logger_test.rb +125 -0
- data/test/unit/model_callbacks_test.rb +116 -0
- data/test/unit/model_import_test.rb +71 -0
- data/test/unit/model_persistence_test.rb +528 -0
- data/test/unit/model_search_test.rb +913 -0
- data/test/unit/results_collection_test.rb +281 -0
- data/test/unit/results_item_test.rb +162 -0
- data/test/unit/rubyext_test.rb +66 -0
- data/test/unit/search_facet_test.rb +153 -0
- data/test/unit/search_filter_test.rb +42 -0
- data/test/unit/search_highlight_test.rb +46 -0
- data/test/unit/search_query_test.rb +301 -0
- data/test/unit/search_scan_test.rb +113 -0
- data/test/unit/search_script_field_test.rb +26 -0
- data/test/unit/search_sort_test.rb +50 -0
- data/test/unit/search_test.rb +499 -0
- data/test/unit/tire_test.rb +126 -0
- data/tire.gemspec +90 -0
- metadata +549 -0
@@ -0,0 +1,894 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
|
5
|
+
class IndexTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "Index" do
|
8
|
+
|
9
|
+
setup do @index = Tire::Index.new 'dummy' end
|
10
|
+
teardown do Tire.configure { reset } end
|
11
|
+
|
12
|
+
should "have a name" do
|
13
|
+
assert_equal 'dummy', @index.name
|
14
|
+
end
|
15
|
+
|
16
|
+
should "have an URL endpoint" do
|
17
|
+
assert_equal "#{Configuration.url}/#{@index.name}", @index.url
|
18
|
+
end
|
19
|
+
|
20
|
+
should "return HTTP response" do
|
21
|
+
assert_respond_to @index, :response
|
22
|
+
|
23
|
+
Configuration.client.expects(:head).returns(mock_response('OK'))
|
24
|
+
@index.exists?
|
25
|
+
assert_equal 'OK', @index.response.body
|
26
|
+
end
|
27
|
+
|
28
|
+
should "return true when exists" do
|
29
|
+
Configuration.client.expects(:head).returns(mock_response(''))
|
30
|
+
assert @index.exists?
|
31
|
+
end
|
32
|
+
|
33
|
+
should "return false when does not exist" do
|
34
|
+
Configuration.client.expects(:head).returns(mock_response('', 404))
|
35
|
+
assert ! @index.exists?
|
36
|
+
end
|
37
|
+
|
38
|
+
should "create new index" do
|
39
|
+
Configuration.client.expects(:post).returns(mock_response('{"ok":true,"acknowledged":true}'))
|
40
|
+
assert @index.create
|
41
|
+
end
|
42
|
+
|
43
|
+
should "not raise exception and just return false when trying to create existing index" do
|
44
|
+
Configuration.client.expects(:post).returns(mock_response('{"error":"IndexAlreadyExistsException[\'dummy\']"}', 400))
|
45
|
+
assert_nothing_raised { assert ! @index.create }
|
46
|
+
end
|
47
|
+
|
48
|
+
should "delete index" do
|
49
|
+
Configuration.client.expects(:delete).returns(mock_response('{"ok":true,"acknowledged":true}'))
|
50
|
+
assert @index.delete
|
51
|
+
end
|
52
|
+
|
53
|
+
should "not raise exception and just return false when deleting non-existing index" do
|
54
|
+
Configuration.client.expects(:delete).returns(mock_response('{"error":"[articles] missing"}', 404))
|
55
|
+
assert_nothing_raised { assert ! @index.delete }
|
56
|
+
end
|
57
|
+
|
58
|
+
should "add an index alias" do
|
59
|
+
Configuration.client.expects(:post).with do |url, payload|
|
60
|
+
url =~ /_aliases/ &&
|
61
|
+
MultiJson.decode(payload)['actions'][0]['add'] == {'index' => 'dummy', 'alias' => 'foo'}
|
62
|
+
end.returns(mock_response('{"ok":true}'))
|
63
|
+
|
64
|
+
@index.add_alias 'foo'
|
65
|
+
end
|
66
|
+
|
67
|
+
should "add an index alias with configuration" do
|
68
|
+
Configuration.client.expects(:post).with do |url, payload|
|
69
|
+
url =~ /_aliases/ &&
|
70
|
+
MultiJson.decode(payload)['actions'][0]['add'] == {'index' => 'dummy', 'alias' => 'foo', 'routing' => 1 }
|
71
|
+
end.returns(mock_response('{"ok":true}'))
|
72
|
+
|
73
|
+
@index.add_alias 'foo', :routing => 1
|
74
|
+
end
|
75
|
+
|
76
|
+
should "delete an index alias" do
|
77
|
+
Configuration.client.expects(:get).returns(mock_response({'dummy' => {'aliases' => {'foo' => {}}}}.to_json))
|
78
|
+
Configuration.client.expects(:post).with do |url, payload|
|
79
|
+
url =~ /_aliases/ &&
|
80
|
+
MultiJson.decode(payload)['actions'][0]['remove'] == {'index' => 'dummy', 'alias' => 'foo'}
|
81
|
+
end.returns(mock_response('{"ok":true}'))
|
82
|
+
|
83
|
+
@index.remove_alias 'foo'
|
84
|
+
end
|
85
|
+
|
86
|
+
should "list aliases for an index" do
|
87
|
+
json = {'dummy' => {'aliases' => {'foo' => {}}}}.to_json
|
88
|
+
Configuration.client.expects(:get).returns(mock_response(json))
|
89
|
+
|
90
|
+
assert_equal ['foo'], @index.aliases.map(&:name)
|
91
|
+
end
|
92
|
+
|
93
|
+
should "return properties of an alias" do
|
94
|
+
json = {'dummy' => { 'aliases' => {'foo' => { 'filter' => { 'term' => { 'user' => 'john' } }}} }}.to_json
|
95
|
+
Configuration.client.expects(:get).returns(mock_response(json))
|
96
|
+
|
97
|
+
assert_equal( { 'term' => {'user' => 'john'} }, @index.aliases('foo').filter )
|
98
|
+
end
|
99
|
+
|
100
|
+
should "refresh the index" do
|
101
|
+
Configuration.client.expects(:post).returns(mock_response('{"ok":true,"_shards":{}}'))
|
102
|
+
assert_nothing_raised { assert @index.refresh }
|
103
|
+
end
|
104
|
+
|
105
|
+
should "open the index" do
|
106
|
+
Configuration.client.expects(:post).returns(mock_response('{"ok":true,"_shards":{}}'))
|
107
|
+
assert_nothing_raised { assert @index.open }
|
108
|
+
end
|
109
|
+
|
110
|
+
should "close the index" do
|
111
|
+
Configuration.client.expects(:post).returns(mock_response('{"ok":true,"_shards":{}}'))
|
112
|
+
assert_nothing_raised { assert @index.close }
|
113
|
+
end
|
114
|
+
|
115
|
+
context "analyze support" do
|
116
|
+
setup do
|
117
|
+
@mock_analyze_response = '{"tokens":[{"token":"foo","start_offset":0,"end_offset":4,"type":"<ALPHANUM>","position":1}]}'
|
118
|
+
end
|
119
|
+
|
120
|
+
should "send text to the Analyze API" do
|
121
|
+
Configuration.client.expects(:get).
|
122
|
+
with("#{@index.url}/_analyze?pretty=true", "foo bar").
|
123
|
+
returns(mock_response(@mock_analyze_response))
|
124
|
+
|
125
|
+
response = @index.analyze("foo bar")
|
126
|
+
assert_equal 1, response['tokens'].size
|
127
|
+
end
|
128
|
+
|
129
|
+
should "properly encode parameters" do
|
130
|
+
Configuration.client.expects(:get).with do |url, payload|
|
131
|
+
url == "#{@index.url}/_analyze?analyzer=whitespace&pretty=true"
|
132
|
+
end.returns(mock_response(@mock_analyze_response))
|
133
|
+
|
134
|
+
@index.analyze("foo bar", :analyzer => 'whitespace')
|
135
|
+
|
136
|
+
Configuration.client.expects(:get).with do |url, payload|
|
137
|
+
url == "#{@index.url}/_analyze?field=title&pretty=true"
|
138
|
+
end.returns(mock_response(@mock_analyze_response))
|
139
|
+
|
140
|
+
@index.analyze("foo bar", :field => 'title')
|
141
|
+
|
142
|
+
Configuration.client.expects(:get).with do |url, payload|
|
143
|
+
url == "#{@index.url}/_analyze?analyzer=keyword&format=text&pretty=true"
|
144
|
+
end.returns(mock_response(@mock_analyze_response))
|
145
|
+
|
146
|
+
@index.analyze("foo bar", :analyzer => 'keyword', :format => 'text')
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
context "mapping" do
|
152
|
+
|
153
|
+
should "create index with mapping" do
|
154
|
+
Configuration.client.expects(:post).returns(mock_response('{"ok":true,"acknowledged":true}'))
|
155
|
+
|
156
|
+
assert @index.create :settings => { :number_of_shards => 1 },
|
157
|
+
:mappings => { :article => {
|
158
|
+
:properties => {
|
159
|
+
:title => { :boost => 2.0,
|
160
|
+
:type => 'string',
|
161
|
+
:store => 'yes',
|
162
|
+
:analyzer => 'snowball' }
|
163
|
+
}
|
164
|
+
}
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
should "return the mapping" do
|
169
|
+
json =<<-JSON
|
170
|
+
{
|
171
|
+
"dummy" : {
|
172
|
+
"article" : {
|
173
|
+
"properties" : {
|
174
|
+
"title" : { "type" : "string", "boost" : 2.0 },
|
175
|
+
"category" : { "type" : "string", "analyzed" : "no" }
|
176
|
+
}
|
177
|
+
}
|
178
|
+
}
|
179
|
+
}
|
180
|
+
JSON
|
181
|
+
Configuration.client.stubs(:get).returns(mock_response(json))
|
182
|
+
|
183
|
+
assert_equal 'string', @index.mapping['article']['properties']['title']['type']
|
184
|
+
assert_equal 2.0, @index.mapping['article']['properties']['title']['boost']
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
context "settings" do
|
190
|
+
|
191
|
+
should "return index settings" do
|
192
|
+
json =<<-JSON
|
193
|
+
{
|
194
|
+
"dummy" : {
|
195
|
+
"settings" : {
|
196
|
+
"index.number_of_shards" : "20",
|
197
|
+
"index.number_of_replicas" : "0"
|
198
|
+
}
|
199
|
+
}
|
200
|
+
}
|
201
|
+
JSON
|
202
|
+
Configuration.client.stubs(:get).returns(mock_response(json))
|
203
|
+
|
204
|
+
assert_equal '20', @index.settings['index.number_of_shards']
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
context "when storing" do
|
210
|
+
|
211
|
+
should "set type from Hash :type property" do
|
212
|
+
Configuration.client.expects(:post).with do |url,document|
|
213
|
+
url == "#{@index.url}/article/"
|
214
|
+
end.returns(mock_response('{"ok":true,"_id":"test"}'))
|
215
|
+
@index.store :type => 'article', :title => 'Test'
|
216
|
+
end
|
217
|
+
|
218
|
+
should "set type from Hash :_type property" do
|
219
|
+
Configuration.client.expects(:post).with do |url,document|
|
220
|
+
url == "#{@index.url}/article/"
|
221
|
+
end.returns(mock_response('{"ok":true,"_id":"test"}'))
|
222
|
+
@index.store :_type => 'article', :title => 'Test'
|
223
|
+
end
|
224
|
+
|
225
|
+
should "set type from Object _type method" do
|
226
|
+
Configuration.client.expects(:post).with do |url,document|
|
227
|
+
url == "#{@index.url}/article/"
|
228
|
+
end.returns(mock_response('{"ok":true,"_id":"test"}'))
|
229
|
+
|
230
|
+
article = Class.new do
|
231
|
+
def _type; 'article'; end
|
232
|
+
def to_indexed_json; "{}"; end
|
233
|
+
end.new
|
234
|
+
@index.store article
|
235
|
+
end
|
236
|
+
|
237
|
+
should "set type from Object type method" do
|
238
|
+
Configuration.client.expects(:post).with do |url,document|
|
239
|
+
url == "#{@index.url}/article/"
|
240
|
+
end.returns(mock_response('{"ok":true,"_id":"test"}'))
|
241
|
+
|
242
|
+
article = Class.new do
|
243
|
+
def type; 'article'; end
|
244
|
+
def to_indexed_json; "{}"; end
|
245
|
+
end.new
|
246
|
+
@index.store article
|
247
|
+
end
|
248
|
+
|
249
|
+
should "properly encode namespaced document types" do
|
250
|
+
Configuration.client.expects(:post).with do |url,document|
|
251
|
+
url == "#{@index.url}/my_namespace%2Fmy_model/"
|
252
|
+
end.returns(mock_response('{"ok":true,"_id":"123"}'))
|
253
|
+
|
254
|
+
module MyNamespace
|
255
|
+
class MyModel
|
256
|
+
def document_type; "my_namespace/my_model"; end
|
257
|
+
def to_indexed_json; "{}"; end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
@index.store MyNamespace::MyModel.new
|
262
|
+
end
|
263
|
+
|
264
|
+
should "set default type" do
|
265
|
+
Configuration.client.expects(:post).with("#{@index.url}/document/", '{"title":"Test"}').returns(mock_response('{"ok":true,"_id":"test"}'))
|
266
|
+
@index.store :title => 'Test'
|
267
|
+
end
|
268
|
+
|
269
|
+
should "call #to_indexed_json on non-String documents" do
|
270
|
+
document = { :title => 'Test' }
|
271
|
+
Configuration.client.expects(:post).returns(mock_response('{"ok":true,"_id":"test"}'))
|
272
|
+
document.expects(:to_indexed_json)
|
273
|
+
@index.store document
|
274
|
+
end
|
275
|
+
|
276
|
+
should "raise error when storing neither String nor object with #to_indexed_json method" do
|
277
|
+
class MyDocument;end; document = MyDocument.new
|
278
|
+
assert_raise(ArgumentError) { @index.store document }
|
279
|
+
end
|
280
|
+
|
281
|
+
should "raise deprecation warning when trying to store a JSON string" do
|
282
|
+
Configuration.client.expects(:post).returns(mock_response('{"ok":true,"_id":"test"}'))
|
283
|
+
@index.store '{"foo" : "bar"}'
|
284
|
+
end
|
285
|
+
|
286
|
+
context "document with ID" do
|
287
|
+
|
288
|
+
should "store Hash it under its ID property" do
|
289
|
+
Configuration.client.expects(:post).with("#{@index.url}/document/123",
|
290
|
+
MultiJson.encode({:id => 123, :title => 'Test'})).
|
291
|
+
returns(mock_response('{"ok":true,"_id":"123"}'))
|
292
|
+
@index.store :id => 123, :title => 'Test'
|
293
|
+
end
|
294
|
+
|
295
|
+
should "store a custom class under its ID property" do
|
296
|
+
Configuration.client.expects(:post).with("#{@index.url}/document/123",
|
297
|
+
{:id => 123, :title => 'Test', :body => 'Lorem'}.to_json).
|
298
|
+
returns(mock_response('{"ok":true,"_id":"123"}'))
|
299
|
+
@index.store Article.new(:id => 123, :title => 'Test', :body => 'Lorem')
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
|
304
|
+
end
|
305
|
+
|
306
|
+
context "when retrieving" do
|
307
|
+
|
308
|
+
setup do
|
309
|
+
Configuration.reset :wrapper
|
310
|
+
|
311
|
+
Configuration.client.stubs(:post).with do |url, payload|
|
312
|
+
url == "#{@index.url}/article/" &&
|
313
|
+
payload =~ /"title":"Test"/
|
314
|
+
end.
|
315
|
+
returns(mock_response('{"ok":true,"_id":"id-1"}'))
|
316
|
+
@index.store :type => 'article', :title => 'Test'
|
317
|
+
end
|
318
|
+
|
319
|
+
should "return document in default wrapper" do
|
320
|
+
Configuration.client.expects(:get).with("#{@index.url}/article/id-1").
|
321
|
+
returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
|
322
|
+
article = @index.retrieve :article, 'id-1'
|
323
|
+
assert_instance_of Results::Item, article
|
324
|
+
assert_equal 'Test', article.title
|
325
|
+
assert_equal 'Test', article[:title]
|
326
|
+
end
|
327
|
+
|
328
|
+
should "return document as a hash" do
|
329
|
+
Configuration.wrapper Hash
|
330
|
+
|
331
|
+
Configuration.client.expects(:get).with("#{@index.url}/article/id-1").
|
332
|
+
returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
|
333
|
+
article = @index.retrieve :article, 'id-1'
|
334
|
+
assert_instance_of Hash, article
|
335
|
+
end
|
336
|
+
|
337
|
+
should "return document in custom wrapper" do
|
338
|
+
Configuration.wrapper Article
|
339
|
+
|
340
|
+
Configuration.client.expects(:get).with("#{@index.url}/article/id-1").
|
341
|
+
returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
|
342
|
+
article = @index.retrieve :article, 'id-1'
|
343
|
+
assert_instance_of Article, article
|
344
|
+
assert_equal 'Test', article.title
|
345
|
+
end
|
346
|
+
|
347
|
+
should "return nil for missing document" do
|
348
|
+
Configuration.client.expects(:get).with("#{@index.url}/article/id-1").
|
349
|
+
returns(mock_response('{"_id":"id-1","exists":false}'))
|
350
|
+
article = @index.retrieve :article, 'id-1'
|
351
|
+
assert_equal nil, article
|
352
|
+
end
|
353
|
+
|
354
|
+
should "raise error when no ID passed" do
|
355
|
+
assert_raise ArgumentError do
|
356
|
+
@index.retrieve 'article', nil
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
should "properly encode document type" do
|
361
|
+
Configuration.client.expects(:get).with("#{@index.url}/my_namespace%2Fmy_model/id-1").
|
362
|
+
returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
|
363
|
+
article = @index.retrieve 'my_namespace/my_model', 'id-1'
|
364
|
+
end
|
365
|
+
|
366
|
+
end
|
367
|
+
|
368
|
+
context "when removing" do
|
369
|
+
|
370
|
+
should "get type from document" do
|
371
|
+
Configuration.client.expects(:delete).with("#{@index.url}/article/1").
|
372
|
+
returns(mock_response('{"ok":true,"_id":"1"}')).twice
|
373
|
+
@index.remove :id => 1, :type => 'article', :title => 'Test'
|
374
|
+
@index.remove :id => 1, :type => 'article', :title => 'Test'
|
375
|
+
end
|
376
|
+
|
377
|
+
should "get namespaced type from document" do
|
378
|
+
Configuration.client.expects(:delete).with("#{@index.url}/articles%2Farticle/1").
|
379
|
+
returns(mock_response('{"ok":true,"_id":"1"}')).twice
|
380
|
+
@index.remove :id => 1, :type => 'articles/article', :title => 'Test'
|
381
|
+
@index.remove :id => 1, :type => 'articles/article', :title => 'Test'
|
382
|
+
end
|
383
|
+
|
384
|
+
should "set default type" do
|
385
|
+
Configuration.client.expects(:delete).with("#{@index.url}/document/1").
|
386
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
387
|
+
@index.remove :id => 1, :title => 'Test'
|
388
|
+
end
|
389
|
+
|
390
|
+
should "get ID from hash" do
|
391
|
+
Configuration.client.expects(:delete).with("#{@index.url}/document/1").
|
392
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
393
|
+
@index.remove :id => 1
|
394
|
+
end
|
395
|
+
|
396
|
+
should "get ID from method" do
|
397
|
+
document = stub('document', :id => 1)
|
398
|
+
Configuration.client.expects(:delete).with("#{@index.url}/document/1").
|
399
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
400
|
+
@index.remove document
|
401
|
+
end
|
402
|
+
|
403
|
+
should "get type and ID from arguments" do
|
404
|
+
Configuration.client.expects(:delete).with("#{@index.url}/article/1").
|
405
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
406
|
+
@index.remove :article, 1
|
407
|
+
end
|
408
|
+
|
409
|
+
should "raise error when no ID passed" do
|
410
|
+
assert_raise ArgumentError do
|
411
|
+
@index.remove :document, nil
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
should "properly encode document type" do
|
416
|
+
Configuration.client.expects(:delete).with("#{@index.url}/my_namespace%2Fmy_model/id-1").
|
417
|
+
returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
|
418
|
+
article = @index.remove 'my_namespace/my_model', 'id-1'
|
419
|
+
end
|
420
|
+
|
421
|
+
end
|
422
|
+
|
423
|
+
context "when updating" do
|
424
|
+
|
425
|
+
should "send payload" do
|
426
|
+
Configuration.client.expects(:post).with do |url,payload|
|
427
|
+
payload = MultiJson.decode(payload)
|
428
|
+
# p [url, payload]
|
429
|
+
assert_equal( "#{@index.url}/document/42/_update", url ) &&
|
430
|
+
assert_not_nil( payload['script'] ) &&
|
431
|
+
assert_not_nil( payload['params'] ) &&
|
432
|
+
assert_equal( '21', payload['params']['bar'] )
|
433
|
+
end.
|
434
|
+
returns(
|
435
|
+
mock_response('{"ok":"true","_index":"dummy","_type":"document","_id":"42","_version":"2"}'))
|
436
|
+
|
437
|
+
assert @index.update('document', '42', {:script => "ctx._source.foo = bar;", :params => { :bar => '21' }})
|
438
|
+
end
|
439
|
+
|
440
|
+
should "send options" do
|
441
|
+
Configuration.client.expects(:post).with do |url,payload|
|
442
|
+
payload = MultiJson.decode(payload)
|
443
|
+
# p [url, payload]
|
444
|
+
assert_equal( "#{@index.url}/document/42/_update?timeout=1000", url ) &&
|
445
|
+
assert_nil( payload['timeout'] )
|
446
|
+
end.
|
447
|
+
returns(
|
448
|
+
mock_response('{"ok":"true","_index":"dummy","_type":"document","_id":"42","_version":"2"}'))
|
449
|
+
assert @index.update('document', '42', {:script => "ctx._source.foo = 'bar'"}, {:timeout => 1000})
|
450
|
+
end
|
451
|
+
|
452
|
+
should "raise error when no type or ID is passed" do
|
453
|
+
assert_raise(ArgumentError) { @index.update('article', nil, :script => 'foobar') }
|
454
|
+
assert_raise(ArgumentError) { @index.update(nil, '123', :script => 'foobar') }
|
455
|
+
end
|
456
|
+
|
457
|
+
should "raise an error when no script is passed" do
|
458
|
+
assert_raise ArgumentError do
|
459
|
+
@index.update('article', "42", {:params => {"foo" => "bar"}})
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
end
|
464
|
+
|
465
|
+
context "when storing in bulk" do
|
466
|
+
# The expected JSON looks like this:
|
467
|
+
#
|
468
|
+
# {"index":{"_index":"dummy","_type":"document","_id":"1"}}
|
469
|
+
# {"id":"1","title":"One"}
|
470
|
+
# {"index":{"_index":"dummy","_type":"document","_id":"2"}}
|
471
|
+
# {"id":"2","title":"Two"}
|
472
|
+
#
|
473
|
+
# See http://www.elasticsearch.org/guide/reference/api/bulk.html
|
474
|
+
|
475
|
+
should "serialize Hashes" do
|
476
|
+
Configuration.client.expects(:post).with do |url, json|
|
477
|
+
url == "#{@index.url}/_bulk" &&
|
478
|
+
json =~ /"_index":"dummy"/ &&
|
479
|
+
json =~ /"_type":"document"/ &&
|
480
|
+
json =~ /"_id":"1"/ &&
|
481
|
+
json =~ /"_id":"2"/ &&
|
482
|
+
json =~ /"id":"1"/ &&
|
483
|
+
json =~ /"id":"2"/ &&
|
484
|
+
json =~ /"title":"One"/ &&
|
485
|
+
json =~ /"title":"Two"/
|
486
|
+
end.returns(mock_response('{}'), 200)
|
487
|
+
|
488
|
+
@index.bulk_store [ {:id => '1', :title => 'One'}, {:id => '2', :title => 'Two'} ]
|
489
|
+
end
|
490
|
+
|
491
|
+
should "serialize ActiveModel instances" do
|
492
|
+
Configuration.client.expects(:post).with do |url, json|
|
493
|
+
url == "#{ActiveModelArticle.index.url}/_bulk" &&
|
494
|
+
json =~ /"_index":"active_model_articles"/ &&
|
495
|
+
json =~ /"_type":"active_model_article"/ &&
|
496
|
+
json =~ /"_id":"1"/ &&
|
497
|
+
json =~ /"_id":"2"/ &&
|
498
|
+
json =~ /"title":"One"/ &&
|
499
|
+
json =~ /"title":"Two"/
|
500
|
+
end.returns(mock_response('{}', 200))
|
501
|
+
|
502
|
+
one = ActiveModelArticle.new 'title' => 'One'; one.id = '1'
|
503
|
+
two = ActiveModelArticle.new 'title' => 'Two'; two.id = '2'
|
504
|
+
|
505
|
+
ActiveModelArticle.index.bulk_store [ one, two ]
|
506
|
+
end
|
507
|
+
|
508
|
+
context "namespaced models" do
|
509
|
+
should "not URL-escape the document_type" do
|
510
|
+
Configuration.client.expects(:post).with do |url, json|
|
511
|
+
# puts url, json
|
512
|
+
url == "#{Configuration.url}/my_namespace_my_models/_bulk" &&
|
513
|
+
json =~ %r|"_index":"my_namespace_my_models"| &&
|
514
|
+
json =~ %r|"_type":"my_namespace/my_model"|
|
515
|
+
end.returns(mock_response('{}', 200))
|
516
|
+
|
517
|
+
module MyNamespace
|
518
|
+
class MyModel
|
519
|
+
def document_type; "my_namespace/my_model"; end
|
520
|
+
def to_indexed_json; "{}"; end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
Tire.index('my_namespace_my_models').bulk_store [ MyNamespace::MyModel.new ]
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
should "try again when an exception occurs" do
|
529
|
+
Configuration.client.expects(:post).returns(mock_response('Server error', 503)).at_least(2)
|
530
|
+
|
531
|
+
assert !@index.bulk_store([ {:id => '1', :title => 'One'}, {:id => '2', :title => 'Two'} ])
|
532
|
+
end
|
533
|
+
|
534
|
+
should "try again and the raise when an exception occurs" do
|
535
|
+
Configuration.client.expects(:post).returns(mock_response('Server error', 503)).at_least(2)
|
536
|
+
|
537
|
+
assert_raise(RuntimeError) do
|
538
|
+
@index.bulk_store([ {:id => '1', :title => 'One'}, {:id => '2', :title => 'Two'} ], {:raise => true})
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
should "try again when a connection error occurs" do
|
543
|
+
Configuration.client.expects(:post).raises(Errno::ECONNREFUSED, "Connection refused - connect(2)").at_least(2)
|
544
|
+
|
545
|
+
assert !@index.bulk_store([ {:id => '1', :title => 'One'}, {:id => '2', :title => 'Two'} ])
|
546
|
+
end
|
547
|
+
|
548
|
+
should "signal exceptions should not be caught" do
|
549
|
+
Configuration.client.expects(:post).raises(Interrupt, "abort then interrupt!")
|
550
|
+
|
551
|
+
assert_raise Interrupt do
|
552
|
+
@index.bulk_store([ {:id => '1', :title => 'One'}, {:id => '2', :title => 'Two'} ])
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
should "display error message when collection item does not have ID" do
|
557
|
+
Configuration.client.expects(:post).with do |url, json|
|
558
|
+
url == "#{ActiveModelArticle.index.url}/_bulk"
|
559
|
+
end.returns(mock_response('success', 200))
|
560
|
+
|
561
|
+
STDERR.expects(:puts).once
|
562
|
+
|
563
|
+
documents = [ { :title => 'Bogus' }, { :title => 'Real', :id => 1 } ]
|
564
|
+
ActiveModelArticle.index.bulk_store documents
|
565
|
+
end
|
566
|
+
|
567
|
+
should "log the response code" do
|
568
|
+
Tire.configure { logger STDERR }
|
569
|
+
Configuration.client.expects(:post).returns(mock_response('{}'), 200)
|
570
|
+
|
571
|
+
Configuration.logger.expects(:log_response).with do |status, took, body|
|
572
|
+
status == 200
|
573
|
+
end
|
574
|
+
|
575
|
+
@index.bulk_store [ {:id => '1', :title => 'One'}, {:id => '2', :title => 'Two'} ]
|
576
|
+
end
|
577
|
+
|
578
|
+
end
|
579
|
+
|
580
|
+
context "when importing" do
|
581
|
+
setup do
|
582
|
+
@index = Tire::Index.new 'import'
|
583
|
+
end
|
584
|
+
|
585
|
+
class ::ImportData
|
586
|
+
DATA = (1..4).to_a
|
587
|
+
|
588
|
+
def self.paginate(options={})
|
589
|
+
options = {:page => 1, :per_page => 1000}.update options
|
590
|
+
DATA.slice( (options[:page]-1)*options[:per_page]...options[:page]*options[:per_page] )
|
591
|
+
end
|
592
|
+
|
593
|
+
def self.each(&block); DATA.each █ end
|
594
|
+
def self.map(&block); DATA.map █ end
|
595
|
+
def self.count; DATA.size; end
|
596
|
+
end
|
597
|
+
|
598
|
+
should "be initialized with a collection" do
|
599
|
+
@index.expects(:bulk_store).returns(:true)
|
600
|
+
|
601
|
+
assert_nothing_raised { @index.import [{ :id => 1, :title => 'Article' }] }
|
602
|
+
end
|
603
|
+
|
604
|
+
should "be initialized with a class and params" do
|
605
|
+
@index.expects(:bulk_store).returns(:true)
|
606
|
+
|
607
|
+
assert_nothing_raised { @index.import ImportData }
|
608
|
+
end
|
609
|
+
|
610
|
+
context "plain collection" do
|
611
|
+
|
612
|
+
should "just store it in bulk" do
|
613
|
+
collection = [{ :id => 1, :title => 'Article' }]
|
614
|
+
@index.expects(:bulk_store).with(collection, {} ).returns(true)
|
615
|
+
|
616
|
+
@index.import collection
|
617
|
+
end
|
618
|
+
|
619
|
+
end
|
620
|
+
|
621
|
+
context "class" do
|
622
|
+
|
623
|
+
should "call the passed method and bulk store the results" do
|
624
|
+
@index.expects(:bulk_store).with { |c, o| c == [1, 2, 3, 4] }.returns(true)
|
625
|
+
|
626
|
+
@index.import ImportData, :method => 'paginate'
|
627
|
+
end
|
628
|
+
|
629
|
+
should "pass the params to the passed method and bulk store the results" do
|
630
|
+
@index.expects(:bulk_store).with { |c,o| c == [1, 2] }.returns(true)
|
631
|
+
@index.expects(:bulk_store).with { |c,o| c == [3, 4] }.returns(true)
|
632
|
+
|
633
|
+
@index.import ImportData, :method => 'paginate', :page => 1, :per_page => 2
|
634
|
+
end
|
635
|
+
|
636
|
+
should "pass the class when method not passed" do
|
637
|
+
@index.expects(:bulk_store).with { |c,o| c == ImportData }.returns(true)
|
638
|
+
|
639
|
+
@index.import ImportData
|
640
|
+
end
|
641
|
+
|
642
|
+
end
|
643
|
+
|
644
|
+
context "with passed block" do
|
645
|
+
|
646
|
+
context "and plain collection" do
|
647
|
+
|
648
|
+
should "allow to manipulate the collection in the block" do
|
649
|
+
Tire::Index.any_instance.expects(:bulk_store).with([{ :id => 1, :title => 'ARTICLE' }], {})
|
650
|
+
|
651
|
+
|
652
|
+
@index.import [{ :id => 1, :title => 'Article' }] do |articles|
|
653
|
+
articles.map { |article| article.update :title => article[:title].upcase }
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
end
|
658
|
+
|
659
|
+
context "and object" do
|
660
|
+
|
661
|
+
should "call the passed block on every batch" do
|
662
|
+
Tire::Index.any_instance.expects(:bulk_store).with { |collection, options| collection == [1, 2] }
|
663
|
+
Tire::Index.any_instance.expects(:bulk_store).with { |collection, options| collection == [3, 4] }
|
664
|
+
|
665
|
+
runs = 0
|
666
|
+
@index.import ImportData, :method => 'paginate', :per_page => 2 do |documents|
|
667
|
+
runs += 1
|
668
|
+
# Don't forget to return the documents at the end of the block
|
669
|
+
documents
|
670
|
+
end
|
671
|
+
|
672
|
+
assert_equal 2, runs
|
673
|
+
end
|
674
|
+
|
675
|
+
should "allow to manipulate the documents in passed block" do
|
676
|
+
Tire::Index.any_instance.expects(:bulk_store).with { |c,o| c == [2, 3] }
|
677
|
+
Tire::Index.any_instance.expects(:bulk_store).with { |c,o| c == [4, 5] }
|
678
|
+
|
679
|
+
@index.import ImportData, :method => :paginate, :per_page => 2 do |documents|
|
680
|
+
# Add 1 to every "document" and return them
|
681
|
+
documents.map { |d| d + 1 }
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
end
|
686
|
+
|
687
|
+
end
|
688
|
+
|
689
|
+
end
|
690
|
+
|
691
|
+
context "when percolating" do
|
692
|
+
|
693
|
+
should "register percolator query as a Hash" do
|
694
|
+
query = { :query => { :query_string => { :query => 'foo' } } }
|
695
|
+
Configuration.client.expects(:put).with do |url, payload|
|
696
|
+
payload = MultiJson.decode(payload)
|
697
|
+
url == "#{Configuration.url}/_percolator/dummy/my-query" &&
|
698
|
+
payload['query']['query_string']['query'] == 'foo'
|
699
|
+
end.
|
700
|
+
returns(mock_response('{
|
701
|
+
"ok" : true,
|
702
|
+
"_index" : "_percolator",
|
703
|
+
"_type" : "dummy",
|
704
|
+
"_id" : "my-query",
|
705
|
+
"_version" : 1
|
706
|
+
}'))
|
707
|
+
|
708
|
+
@index.register_percolator_query 'my-query', query
|
709
|
+
end
|
710
|
+
|
711
|
+
should "register percolator query as a block" do
|
712
|
+
Configuration.client.expects(:put).with do |url, payload|
|
713
|
+
payload = MultiJson.decode(payload)
|
714
|
+
url == "#{Configuration.url}/_percolator/dummy/my-query" &&
|
715
|
+
payload['query']['query_string']['query'] == 'foo'
|
716
|
+
end.
|
717
|
+
returns(mock_response('{
|
718
|
+
"ok" : true,
|
719
|
+
"_index" : "_percolator",
|
720
|
+
"_type" : "dummy",
|
721
|
+
"_id" : "my-query",
|
722
|
+
"_version" : 1
|
723
|
+
}'))
|
724
|
+
|
725
|
+
@index.register_percolator_query 'my-query' do
|
726
|
+
string 'foo'
|
727
|
+
end
|
728
|
+
end
|
729
|
+
|
730
|
+
should "register percolator query with a key" do
|
731
|
+
query = { :query => { :query_string => { :query => 'foo' } },
|
732
|
+
:tags => ['alert'] }
|
733
|
+
|
734
|
+
Configuration.client.expects(:put).with do |url, payload|
|
735
|
+
payload = MultiJson.decode(payload)
|
736
|
+
url == "#{Configuration.url}/_percolator/dummy/my-query" &&
|
737
|
+
payload['query']['query_string']['query'] == 'foo' &&
|
738
|
+
payload['tags'] == ['alert']
|
739
|
+
end.
|
740
|
+
returns(mock_response('{
|
741
|
+
"ok" : true,
|
742
|
+
"_index" : "_percolator",
|
743
|
+
"_type" : "dummy",
|
744
|
+
"_id" : "my-query",
|
745
|
+
"_version" : 1
|
746
|
+
}'))
|
747
|
+
|
748
|
+
assert @index.register_percolator_query('my-query', query)
|
749
|
+
end
|
750
|
+
|
751
|
+
should "unregister percolator query" do
|
752
|
+
Configuration.client.expects(:delete).with("#{Configuration.url}/_percolator/dummy/my-query").
|
753
|
+
returns(mock_response('{"ok":true,"acknowledged":true}'))
|
754
|
+
assert @index.unregister_percolator_query('my-query')
|
755
|
+
end
|
756
|
+
|
757
|
+
should "percolate document against all registered queries" do
|
758
|
+
Configuration.client.expects(:get).with do |url,payload|
|
759
|
+
payload = MultiJson.decode(payload)
|
760
|
+
url == "#{@index.url}/document/_percolate" &&
|
761
|
+
payload['doc']['title'] == 'Test'
|
762
|
+
end.
|
763
|
+
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
764
|
+
|
765
|
+
matches = @index.percolate :title => 'Test'
|
766
|
+
assert_equal ["alerts"], matches
|
767
|
+
end
|
768
|
+
|
769
|
+
should "percolate a typed document against all registered queries" do
|
770
|
+
Configuration.client.expects(:get).with do |url,payload|
|
771
|
+
payload = MultiJson.decode(payload)
|
772
|
+
url == "#{@index.url}/article/_percolate" &&
|
773
|
+
payload['doc']['title'] == 'Test'
|
774
|
+
end.
|
775
|
+
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
776
|
+
|
777
|
+
matches = @index.percolate :type => 'article', :title => 'Test'
|
778
|
+
assert_equal ["alerts"], matches
|
779
|
+
end
|
780
|
+
|
781
|
+
should "percolate document against specific queries" do
|
782
|
+
Configuration.client.expects(:get).with do |url,payload|
|
783
|
+
payload = MultiJson.decode(payload)
|
784
|
+
# p [url, payload]
|
785
|
+
url == "#{@index.url}/document/_percolate" &&
|
786
|
+
payload['doc']['title'] == 'Test' &&
|
787
|
+
payload['query']['query_string']['query'] == 'tag:alerts'
|
788
|
+
end.
|
789
|
+
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
790
|
+
|
791
|
+
matches = @index.percolate(:title => 'Test') { string 'tag:alerts' }
|
792
|
+
assert_equal ["alerts"], matches
|
793
|
+
end
|
794
|
+
|
795
|
+
context "while storing document" do
|
796
|
+
|
797
|
+
should "percolate document against all registered queries" do
|
798
|
+
Configuration.client.expects(:post).
|
799
|
+
with do |url, payload|
|
800
|
+
url == "#{@index.url}/article/?percolate=*" &&
|
801
|
+
payload =~ /"title":"Test"/
|
802
|
+
end.
|
803
|
+
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
804
|
+
@index.store( {:type => 'article', :title => 'Test'}, {:percolate => true} )
|
805
|
+
end
|
806
|
+
|
807
|
+
should "percolate document against specific queries" do
|
808
|
+
Configuration.client.expects(:post).
|
809
|
+
with do |url, payload|
|
810
|
+
url == "#{@index.url}/article/?percolate=tag:alerts" &&
|
811
|
+
payload =~ /"title":"Test"/
|
812
|
+
end.
|
813
|
+
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
814
|
+
response = @index.store( {:type => 'article', :title => 'Test'}, {:percolate => 'tag:alerts'} )
|
815
|
+
assert_equal response['matches'], ['alerts']
|
816
|
+
end
|
817
|
+
|
818
|
+
end
|
819
|
+
|
820
|
+
end
|
821
|
+
|
822
|
+
context "reindexing" do
|
823
|
+
setup do
|
824
|
+
@results = {
|
825
|
+
"_scroll_id" => "abc123",
|
826
|
+
"took" => 3,
|
827
|
+
"hits" => {
|
828
|
+
"total" => 10,
|
829
|
+
"hits" => [
|
830
|
+
{ "_id" => "1", "_source" => { "title" => "Test" } }
|
831
|
+
]
|
832
|
+
}
|
833
|
+
}
|
834
|
+
end
|
835
|
+
|
836
|
+
should "perform bulk store in the new index" do
|
837
|
+
Index.any_instance.stubs(:exists?).returns(true)
|
838
|
+
Search::Scan.any_instance.stubs(:__perform)
|
839
|
+
Search::Scan.any_instance.
|
840
|
+
expects(:results).
|
841
|
+
returns(Results::Collection.new(@results)).
|
842
|
+
then.
|
843
|
+
returns(Results::Collection.new(@results.merge('hits' => {'hits' => []}))).
|
844
|
+
at_least_once
|
845
|
+
|
846
|
+
Index.any_instance.expects(:bulk_store).once
|
847
|
+
|
848
|
+
@index.reindex 'whammy'
|
849
|
+
end
|
850
|
+
|
851
|
+
should "create the new index if it does not exist" do
|
852
|
+
options = { :settings => { :number_of_shards => 1 } }
|
853
|
+
|
854
|
+
Index.any_instance.stubs(:exists?).returns(false)
|
855
|
+
Search::Scan.any_instance.stubs(:__perform)
|
856
|
+
Search::Scan.any_instance.
|
857
|
+
expects(:results).
|
858
|
+
returns(Results::Collection.new(@results)).
|
859
|
+
then.
|
860
|
+
returns(Results::Collection.new(@results.merge('hits' => {'hits' => []}))).
|
861
|
+
at_least_once
|
862
|
+
|
863
|
+
Index.any_instance.expects(:create).with(options).once
|
864
|
+
|
865
|
+
@index.reindex 'whammy', options
|
866
|
+
end
|
867
|
+
|
868
|
+
end
|
869
|
+
|
870
|
+
context "when accessing the variables from outer scope" do
|
871
|
+
|
872
|
+
should "access the variables" do
|
873
|
+
@my_title = 'Title From Outer Space'
|
874
|
+
|
875
|
+
def index_something
|
876
|
+
@tags = ['block', 'scope', 'revenge']
|
877
|
+
|
878
|
+
Index.any_instance.expects(:store).with(:title => 'Title From Outer Space', :tags => ['block', 'scope', 'revenge'])
|
879
|
+
|
880
|
+
Tire::Index.new 'outer-space' do |index|
|
881
|
+
index.store :title => @my_title, :tags => @tags
|
882
|
+
end
|
883
|
+
end
|
884
|
+
|
885
|
+
index_something
|
886
|
+
end
|
887
|
+
|
888
|
+
end
|
889
|
+
|
890
|
+
end
|
891
|
+
|
892
|
+
end
|
893
|
+
|
894
|
+
end
|