tire 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.gitignore +1 -1
  2. data/.yardopts +1 -0
  3. data/README.markdown +2 -2
  4. data/examples/rails-application-template.rb +20 -6
  5. data/lib/tire.rb +2 -0
  6. data/lib/tire/alias.rb +1 -1
  7. data/lib/tire/configuration.rb +8 -0
  8. data/lib/tire/dsl.rb +69 -2
  9. data/lib/tire/index.rb +33 -20
  10. data/lib/tire/model/indexing.rb +7 -1
  11. data/lib/tire/model/persistence.rb +7 -4
  12. data/lib/tire/model/persistence/attributes.rb +1 -1
  13. data/lib/tire/model/persistence/finders.rb +4 -16
  14. data/lib/tire/model/search.rb +21 -8
  15. data/lib/tire/multi_search.rb +263 -0
  16. data/lib/tire/results/collection.rb +78 -49
  17. data/lib/tire/results/item.rb +6 -3
  18. data/lib/tire/results/pagination.rb +15 -1
  19. data/lib/tire/rubyext/ruby_1_8.rb +1 -7
  20. data/lib/tire/rubyext/uri_escape.rb +74 -0
  21. data/lib/tire/search.rb +33 -11
  22. data/lib/tire/search/facet.rb +8 -3
  23. data/lib/tire/search/filter.rb +1 -1
  24. data/lib/tire/search/highlight.rb +1 -1
  25. data/lib/tire/search/queries/match.rb +40 -0
  26. data/lib/tire/search/query.rb +42 -6
  27. data/lib/tire/search/scan.rb +1 -1
  28. data/lib/tire/search/script_field.rb +1 -1
  29. data/lib/tire/search/sort.rb +1 -1
  30. data/lib/tire/tasks.rb +17 -14
  31. data/lib/tire/version.rb +26 -8
  32. data/test/integration/active_record_searchable_test.rb +248 -129
  33. data/test/integration/boosting_queries_test.rb +32 -0
  34. data/test/integration/custom_score_queries_test.rb +1 -0
  35. data/test/integration/dsl_search_test.rb +9 -1
  36. data/test/integration/facets_test.rb +19 -6
  37. data/test/integration/match_query_test.rb +79 -0
  38. data/test/integration/multi_search_test.rb +114 -0
  39. data/test/integration/persistent_model_test.rb +58 -0
  40. data/test/models/article.rb +1 -1
  41. data/test/models/persistent_article_in_index.rb +16 -0
  42. data/test/models/persistent_article_with_defaults.rb +4 -3
  43. data/test/test_helper.rb +3 -1
  44. data/test/unit/configuration_test.rb +10 -0
  45. data/test/unit/index_test.rb +69 -27
  46. data/test/unit/model_initialization_test.rb +31 -0
  47. data/test/unit/model_persistence_test.rb +21 -7
  48. data/test/unit/model_search_test.rb +56 -5
  49. data/test/unit/multi_search_test.rb +304 -0
  50. data/test/unit/results_collection_test.rb +42 -2
  51. data/test/unit/results_item_test.rb +4 -0
  52. data/test/unit/search_facet_test.rb +35 -11
  53. data/test/unit/search_query_test.rb +96 -0
  54. data/test/unit/search_test.rb +60 -3
  55. data/test/unit/tire_test.rb +14 -0
  56. data/tire.gemspec +0 -1
  57. metadata +75 -44
@@ -126,21 +126,26 @@ module Tire
126
126
  assert_equal 1, response['tokens'].size
127
127
  end
128
128
 
129
- should "properly encode parameters" do
129
+ should "properly encode parameters for analyzer" do
130
130
  Configuration.client.expects(:get).with do |url, payload|
131
- url == "#{@index.url}/_analyze?analyzer=whitespace&pretty=true"
131
+ assert_equal "#{@index.url}/_analyze?analyzer=whitespace&pretty=true", url
132
132
  end.returns(mock_response(@mock_analyze_response))
133
133
 
134
134
  @index.analyze("foo bar", :analyzer => 'whitespace')
135
135
 
136
+ end
137
+
138
+ should "properly encode parameters for field" do
136
139
  Configuration.client.expects(:get).with do |url, payload|
137
- url == "#{@index.url}/_analyze?field=title&pretty=true"
140
+ assert_equal "#{@index.url}/_analyze?field=title&pretty=true", url
138
141
  end.returns(mock_response(@mock_analyze_response))
139
142
 
140
143
  @index.analyze("foo bar", :field => 'title')
144
+ end
141
145
 
146
+ should "properly encode format parameter" do
142
147
  Configuration.client.expects(:get).with do |url, payload|
143
- url == "#{@index.url}/_analyze?analyzer=keyword&format=text&pretty=true"
148
+ assert_equal "#{@index.url}/_analyze?analyzer=keyword&format=text&pretty=true", url
144
149
  end.returns(mock_response(@mock_analyze_response))
145
150
 
146
151
  @index.analyze("foo bar", :analyzer => 'keyword', :format => 'text')
@@ -210,21 +215,21 @@ module Tire
210
215
 
211
216
  should "set type from Hash :type property" do
212
217
  Configuration.client.expects(:post).with do |url,document|
213
- url == "#{@index.url}/article/"
218
+ assert_equal "#{@index.url}/article/", url
214
219
  end.returns(mock_response('{"ok":true,"_id":"test"}'))
215
220
  @index.store :type => 'article', :title => 'Test'
216
221
  end
217
222
 
218
223
  should "set type from Hash :_type property" do
219
224
  Configuration.client.expects(:post).with do |url,document|
220
- url == "#{@index.url}/article/"
225
+ assert_equal "#{@index.url}/article/", url
221
226
  end.returns(mock_response('{"ok":true,"_id":"test"}'))
222
227
  @index.store :_type => 'article', :title => 'Test'
223
228
  end
224
229
 
225
230
  should "set type from Object _type method" do
226
231
  Configuration.client.expects(:post).with do |url,document|
227
- url == "#{@index.url}/article/"
232
+ assert_equal "#{@index.url}/article/", url
228
233
  end.returns(mock_response('{"ok":true,"_id":"test"}'))
229
234
 
230
235
  article = Class.new do
@@ -236,7 +241,7 @@ module Tire
236
241
 
237
242
  should "set type from Object type method" do
238
243
  Configuration.client.expects(:post).with do |url,document|
239
- url == "#{@index.url}/article/"
244
+ assert_equal "#{@index.url}/article/", url
240
245
  end.returns(mock_response('{"ok":true,"_id":"test"}'))
241
246
 
242
247
  article = Class.new do
@@ -248,7 +253,7 @@ module Tire
248
253
 
249
254
  should "properly encode namespaced document types" do
250
255
  Configuration.client.expects(:post).with do |url,document|
251
- url == "#{@index.url}/my_namespace%2Fmy_model/"
256
+ assert_equal "#{@index.url}/my_namespace%2Fmy_model/", url
252
257
  end.returns(mock_response('{"ok":true,"_id":"123"}'))
253
258
 
254
259
  module MyNamespace
@@ -363,6 +368,29 @@ module Tire
363
368
  article = @index.retrieve 'my_namespace/my_model', 'id-1'
364
369
  end
365
370
 
371
+ should "allow to set routing" do
372
+ Configuration.client.expects(:get).with("#{@index.url}/article/id-1?routing=foo").
373
+ returns(mock_response('{"_id":"id-1"'))
374
+ article = @index.retrieve :article, 'id-1', :routing => 'foo'
375
+ end
376
+
377
+ should "allow to set routing and fields" do
378
+ Configuration.client.expects(:get).with do |url|
379
+ assert url.include?('routing=foo'), url
380
+ assert url.include?('fields=name'), url
381
+ end.returns(mock_response('{"_id":"id-1"'))
382
+
383
+ article = @index.retrieve :article, 'id-1', :routing => 'foo', :fields => 'name'
384
+ end
385
+
386
+ should "allow to set preference" do
387
+ Configuration.client.expects(:get).with do |url|
388
+ assert url.include?('preference=foo'), url
389
+ end.returns(mock_response('{"_id":"id-1"'))
390
+
391
+ article = @index.retrieve :article, 'id-1', :preference => 'foo'
392
+ end
393
+
366
394
  end
367
395
 
368
396
  context "when removing" do
@@ -694,8 +722,9 @@ module Tire
694
722
  query = { :query => { :query_string => { :query => 'foo' } } }
695
723
  Configuration.client.expects(:put).with do |url, payload|
696
724
  payload = MultiJson.decode(payload)
697
- url == "#{Configuration.url}/_percolator/dummy/my-query" &&
698
- payload['query']['query_string']['query'] == 'foo'
725
+ assert_equal "#{Configuration.url}/_percolator/dummy/my-query",
726
+ url
727
+ assert_equal 'foo', payload['query']['query_string']['query']
699
728
  end.
700
729
  returns(mock_response('{
701
730
  "ok" : true,
@@ -711,8 +740,9 @@ module Tire
711
740
  should "register percolator query as a block" do
712
741
  Configuration.client.expects(:put).with do |url, payload|
713
742
  payload = MultiJson.decode(payload)
714
- url == "#{Configuration.url}/_percolator/dummy/my-query" &&
715
- payload['query']['query_string']['query'] == 'foo'
743
+ assert_equal "#{Configuration.url}/_percolator/dummy/my-query",
744
+ url
745
+ assert_equal 'foo', payload['query']['query_string']['query']
716
746
  end.
717
747
  returns(mock_response('{
718
748
  "ok" : true,
@@ -733,9 +763,10 @@ module Tire
733
763
 
734
764
  Configuration.client.expects(:put).with do |url, payload|
735
765
  payload = MultiJson.decode(payload)
736
- url == "#{Configuration.url}/_percolator/dummy/my-query" &&
737
- payload['query']['query_string']['query'] == 'foo' &&
738
- payload['tags'] == ['alert']
766
+ assert_equal "#{Configuration.url}/_percolator/dummy/my-query",
767
+ url
768
+ assert_equal 'foo', payload['query']['query_string']['query']
769
+ assert_equal ['alert'], payload['tags']
739
770
  end.
740
771
  returns(mock_response('{
741
772
  "ok" : true,
@@ -757,8 +788,8 @@ module Tire
757
788
  should "percolate document against all registered queries" do
758
789
  Configuration.client.expects(:get).with do |url,payload|
759
790
  payload = MultiJson.decode(payload)
760
- url == "#{@index.url}/document/_percolate" &&
761
- payload['doc']['title'] == 'Test'
791
+ assert_equal "#{@index.url}/document/_percolate", url
792
+ assert_equal 'Test', payload['doc']['title']
762
793
  end.
763
794
  returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
764
795
 
@@ -769,8 +800,8 @@ module Tire
769
800
  should "percolate a typed document against all registered queries" do
770
801
  Configuration.client.expects(:get).with do |url,payload|
771
802
  payload = MultiJson.decode(payload)
772
- url == "#{@index.url}/article/_percolate" &&
773
- payload['doc']['title'] == 'Test'
803
+ assert_equal "#{@index.url}/article/_percolate", url
804
+ assert_equal 'Test', payload['doc']['title']
774
805
  end.
775
806
  returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
776
807
 
@@ -782,9 +813,9 @@ module Tire
782
813
  Configuration.client.expects(:get).with do |url,payload|
783
814
  payload = MultiJson.decode(payload)
784
815
  # p [url, payload]
785
- url == "#{@index.url}/document/_percolate" &&
786
- payload['doc']['title'] == 'Test' &&
787
- payload['query']['query_string']['query'] == 'tag:alerts'
816
+ assert_equal "#{@index.url}/document/_percolate", url
817
+ assert_equal 'Test', payload['doc']['title']
818
+ assert_equal 'tag:alerts', payload['query']['query_string']['query']
788
819
  end.
789
820
  returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
790
821
 
@@ -797,8 +828,7 @@ module Tire
797
828
  should "percolate document against all registered queries" do
798
829
  Configuration.client.expects(:post).
799
830
  with do |url, payload|
800
- url == "#{@index.url}/article/?percolate=*" &&
801
- payload =~ /"title":"Test"/
831
+ assert_equal "#{@index.url}/article/?percolate=%2A", url
802
832
  end.
803
833
  returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
804
834
  @index.store( {:type => 'article', :title => 'Test'}, {:percolate => true} )
@@ -807,8 +837,7 @@ module Tire
807
837
  should "percolate document against specific queries" do
808
838
  Configuration.client.expects(:post).
809
839
  with do |url, payload|
810
- url == "#{@index.url}/article/?percolate=tag:alerts" &&
811
- payload =~ /"title":"Test"/
840
+ assert_equal "#{@index.url}/article/?percolate=tag%3Aalerts", url
812
841
  end.
813
842
  returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
814
843
  response = @index.store( {:type => 'article', :title => 'Test'}, {:percolate => 'tag:alerts'} )
@@ -819,6 +848,19 @@ module Tire
819
848
 
820
849
  end
821
850
 
851
+ context "when passing parent document ID" do
852
+
853
+ should "set the :parent option in the request parameters" do
854
+ Configuration.client.expects(:post).
855
+ with do |url, payload|
856
+ assert_equal "#{Configuration.url}/dummy/document/?parent=1234", url
857
+ end.
858
+ returns(mock_response('{"ok":true,"_id":"test"}'))
859
+
860
+ @index.store({:title => 'Test'}, {:parent => 1234})
861
+ end
862
+ end
863
+
822
864
  context "reindexing" do
823
865
  setup do
824
866
  @results = {
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ class ModelWithIncorrectMapping
4
+ extend ActiveModel::Naming
5
+ include Tire::Model::Search
6
+ include Tire::Model::Callbacks
7
+
8
+ tire do
9
+ mapping do
10
+ indexes :title, :type => 'boo'
11
+ end
12
+ end
13
+ end
14
+
15
+ module Tire
16
+ module Model
17
+
18
+ class ModelInitializationTest < Test::Unit::TestCase
19
+
20
+ context "Model initialization" do
21
+
22
+ should "display a warning when creating the index fails" do
23
+ STDERR.expects(:puts)
24
+ result = ModelWithIncorrectMapping.create_elasticsearch_index
25
+ assert ! result, result.inspect
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -24,16 +24,16 @@ module Tire
24
24
  setup do
25
25
  Model::Search.index_prefix 'prefix'
26
26
  end
27
-
27
+
28
28
  teardown do
29
29
  Model::Search.index_prefix nil
30
30
  end
31
-
31
+
32
32
  should "have configured prefix in index_name" do
33
33
  assert_equal 'prefix_persistent_articles', PersistentArticle.index_name
34
34
  assert_equal 'prefix_persistent_articles', PersistentArticle.new(:title => 'Test').index_name
35
35
  end
36
-
36
+
37
37
  end
38
38
 
39
39
  should "have document_type" do
@@ -125,8 +125,13 @@ module Tire
125
125
  assert_equal ids.size, documents.count
126
126
  end
127
127
 
128
- should "find all documents" do
129
- Configuration.client.stubs(:get).returns(mock_response(@find_all.to_json))
128
+ should "find all documents with correct type" do
129
+ Configuration.client.expects(:get).
130
+ with do |url,payload|
131
+ assert_equal "#{Configuration.url}/persistent_articles/persistent_article/_search", url
132
+ end.
133
+ times(3).
134
+ returns(mock_response(@find_all.to_json))
130
135
  documents = PersistentArticle.all
131
136
 
132
137
  assert_equal 3, documents.count
@@ -134,8 +139,12 @@ module Tire
134
139
  assert_equal PersistentArticle.find(:all).map { |e| e.id }, PersistentArticle.all.map { |e| e.id }
135
140
  end
136
141
 
137
- should "find first document" do
138
- Configuration.client.expects(:get).returns(mock_response(@find_first.to_json))
142
+ should "find first document with correct type" do
143
+ Configuration.client.expects(:get).
144
+ with do |url,payload|
145
+ assert_equal "#{Configuration.url}/persistent_articles/persistent_article/_search?size=1", url
146
+ end.
147
+ returns(mock_response(@find_first.to_json))
139
148
  document = PersistentArticle.first
140
149
 
141
150
  assert_equal 'First', document.attributes['title']
@@ -201,6 +210,11 @@ module Tire
201
210
  assert_equal false, article.hidden
202
211
  end
203
212
 
213
+ should "evaluate lambdas as default values" do
214
+ article = PersistentArticleWithDefaults.new
215
+ assert_equal Time.now.year, article.created_at.year
216
+ end
217
+
204
218
  should "not affect default value" do
205
219
  article = PersistentArticleWithDefaults.new :title => 'Test'
206
220
  article.tags << "ruby"
@@ -19,7 +19,7 @@ module Tire
19
19
  context "Model::Search" do
20
20
 
21
21
  setup do
22
- @stub = stub('search') { stubs(:query).returns(self); stubs(:perform).returns(self); stubs(:results).returns([]) }
22
+ @stub = stub('search') { stubs(:query).returns(self); stubs(:perform).returns(self); stubs(:results).returns([]); stubs(:size).returns(true) }
23
23
  Tire::Index.any_instance.stubs(:exists?).returns(false)
24
24
  end
25
25
 
@@ -39,7 +39,7 @@ module Tire
39
39
  should "limit searching in index for documents matching the model 'document_type'" do
40
40
  Tire::Search::Search.
41
41
  expects(:new).
42
- with(ActiveModelArticle.index_name, { :type => ActiveModelArticle.document_type }).
42
+ with('active_model_articles', { :type => 'active_model_article' }).
43
43
  returns(@stub).
44
44
  twice
45
45
 
@@ -121,6 +121,16 @@ module Tire
121
121
  assert_equal 'Article', document.title
122
122
  end
123
123
 
124
+ should "not pass the search option as URL parameter" do
125
+ Configuration.client.
126
+ expects(:get).with do |url, payload|
127
+ assert ! url.include?('sort')
128
+ end.
129
+ returns( mock_response({ 'hits' => { 'hits' => [] } }.to_json) )
130
+
131
+ ActiveModelArticle.search(@q, :sort => 'title:DESC').results
132
+ end
133
+
124
134
  context "searching with a block" do
125
135
  setup do
126
136
  Tire::Search::Search.any_instance.expects(:perform).returns(@stub)
@@ -191,14 +201,14 @@ module Tire
191
201
  should "allow to specify sort direction" do
192
202
  Tire::Search::Sort.any_instance.expects(:by).with('title', 'DESC')
193
203
 
194
- ActiveModelArticle.search @q, :order => 'title DESC'
204
+ ActiveModelArticle.search @q, :order => 'title:DESC'
195
205
  end
196
206
 
197
207
  should "allow to specify more fields to sort on" do
198
208
  Tire::Search::Sort.any_instance.expects(:by).with('title', 'DESC')
199
209
  Tire::Search::Sort.any_instance.expects(:by).with('author.name', nil)
200
210
 
201
- ActiveModelArticle.search @q, :order => ['title DESC', 'author.name']
211
+ ActiveModelArticle.search @q, :order => ['title:DESC', 'author.name']
202
212
  end
203
213
 
204
214
  should "allow to specify number of results per page" do
@@ -237,6 +247,25 @@ module Tire
237
247
 
238
248
  end
239
249
 
250
+ context "multi search" do
251
+
252
+ should "perform search request within corresponding index and type" do
253
+ Tire::Search::Multi::Search.
254
+ expects(:new).
255
+ with do |index, options, block|
256
+ assert_equal 'active_model_articles', index
257
+ assert_equal 'active_model_article', options[:type]
258
+ end.
259
+ returns( mock(:results => []) )
260
+
261
+ ActiveModelArticle.multi_search do
262
+ search 'foo'
263
+ search 'xoo'
264
+ end
265
+ end
266
+
267
+ end
268
+
240
269
  should "not set callback when hooks are missing" do
241
270
  @model = ActiveModelArticle.new
242
271
  @model.expects(:update_elasticsearch_index).never
@@ -610,7 +639,7 @@ module Tire
610
639
 
611
640
  end
612
641
 
613
- should "serialize mapped properties when mapping procs are set" do
642
+ should "evaluate :as mapping options passed as strings or procs" do
614
643
  class ::ModelWithMappingProcs
615
644
  extend ActiveModel::Naming
616
645
  extend ActiveModel::Callbacks
@@ -646,6 +675,28 @@ module Tire
646
675
  assert_equal 3, document['three']
647
676
  end
648
677
 
678
+ should "index :as mapping options passed as arbitrary objects" do
679
+ class ::ModelWithMappingOptionAsObject
680
+ extend ActiveModel::Naming
681
+ extend ActiveModel::Callbacks
682
+ include ActiveModel::Serialization
683
+ include Tire::Model::Search
684
+
685
+ mapping do
686
+ indexes :one, :as => [1, 2, 3]
687
+ end
688
+
689
+ attr_reader :attributes
690
+
691
+ def initialize(attributes = {}); @attributes = attributes; end
692
+ end
693
+
694
+ model = ::ModelWithMappingOptionAsObject.new
695
+ document = MultiJson.decode(model.to_indexed_json)
696
+
697
+ assert_equal [1, 2, 3], document['one']
698
+ end
699
+
649
700
  end
650
701
 
651
702
  context "with percolation" do
@@ -0,0 +1,304 @@
1
+ require 'test_helper'
2
+
3
+ module Tire
4
+ class MultiSearchTest < Test::Unit::TestCase
5
+
6
+ context "Multi::Search" do
7
+ setup { Configuration.reset }
8
+
9
+ should "be initialized with index" do
10
+ @search = Tire::Search::Multi::Search.new 'foo'
11
+ assert_equal ['foo'], @search.indices
12
+ end
13
+
14
+ context "search definition" do
15
+ setup do
16
+ @search_definitions = Tire::Search::Multi::SearchDefinitions.new
17
+ end
18
+
19
+ should "be enumerable" do
20
+ assert_respond_to @search_definitions, :each
21
+ assert_respond_to @search_definitions, :size
22
+ end
23
+
24
+ should "allow to add definition" do
25
+ @search_definitions << { :name => 'foo', :search => { 'query' => 'bar' } }
26
+ assert_equal 1, @search_definitions.size
27
+ end
28
+
29
+ should "return definition" do
30
+ @search_definitions << { :name => 'foo', :search => { 'query' => 'bar' } }
31
+ assert_equal 'bar', @search_definitions['foo']['query']
32
+ end
33
+
34
+ should "have names" do
35
+ @search_definitions << { :name => 'foo', :search => { 'query' => 'bar' } }
36
+ assert_equal ['foo'], @search_definitions.names
37
+ end
38
+ end
39
+
40
+ context "search results" do
41
+ setup do
42
+ @search_definitions = Tire::Search::Multi::SearchDefinitions.new
43
+ @search_definitions << { :name => 'foo', :search => Tire::Search::Search.new }
44
+ @responses = [{ 'hits' => { 'hits' => [{'_id' => 1},
45
+ {'_id' => 2},
46
+ {'_id' => 3}] }
47
+ }]
48
+
49
+ @results = Tire::Search::Multi::Results.new @search_definitions, @responses
50
+ end
51
+
52
+ should "be enumerable" do
53
+ assert_respond_to @results, :each
54
+ assert_respond_to @results, :each_pair
55
+ assert_respond_to @results, :each_with_index
56
+ assert_respond_to @results, :size
57
+ end
58
+
59
+ should "return named results" do
60
+ assert_instance_of Tire::Results::Collection, @results['foo']
61
+ assert_equal 1, @results['foo'].first.id
62
+ end
63
+
64
+ should "return nil for incorrect index" do
65
+ assert_nil @results['moo']
66
+ assert_nil @results[999]
67
+ end
68
+
69
+ should "iterate over results" do
70
+ @results.each do |results|
71
+ assert_instance_of Tire::Results::Collection, results
72
+ end
73
+ end
74
+
75
+ should "iterate over named results" do
76
+ @results.each_pair do |name, results|
77
+ assert_equal 'foo', name
78
+ assert_instance_of Tire::Results::Collection, results
79
+ end
80
+ end
81
+
82
+ should "be serializable to Hash" do
83
+ assert_instance_of Tire::Results::Collection, @results.to_hash['foo']
84
+ end
85
+
86
+ should "pass search options to collection" do
87
+ @search_definitions = Tire::Search::Multi::SearchDefinitions.new
88
+ @search_definitions << { :name => 'foo', :search => Tire::Search::Search.new(nil, :foo => 'bar') }
89
+ @responses = [{ 'hits' => { 'hits' => [{'_id' => 1}] } }]
90
+
91
+ @results = Tire::Search::Multi::Results.new @search_definitions, @responses
92
+
93
+ assert_equal 'bar', @results['foo'].options[:foo]
94
+ end
95
+
96
+ context "error responses" do
97
+ setup do
98
+ @search_definitions = Tire::Search::Multi::SearchDefinitions.new
99
+ @search_definitions << { :name => 'foo', :search => Tire::Search::Search.new }
100
+ @search_definitions << { :name => 'xoo', :search => Tire::Search::Search.new }
101
+ @responses = [{ 'hits' => { 'hits' => [{'_id' => 1},
102
+ {'_id' => 2},
103
+ {'_id' => 3}] }
104
+ },
105
+ {'error' => 'SearchPhaseExecutionException ...'}
106
+ ]
107
+
108
+ @results = Tire::Search::Multi::Results.new @search_definitions, @responses
109
+ end
110
+
111
+ should "return success/failure state" do
112
+ assert_equal true, @results['foo'].success?
113
+ assert_equal 3, @results['foo'].size
114
+ assert_equal true, @results['xoo'].failure?
115
+ end
116
+ end
117
+ end
118
+
119
+ context "URL" do
120
+
121
+ should "have no index and type by default" do
122
+ @search = Tire::Search::Multi::Search.new
123
+ assert_equal '/_msearch', @search.path
124
+ end
125
+
126
+ should "have an index" do
127
+ @search = Tire::Search::Multi::Search.new 'foo'
128
+ assert_equal '/foo/_msearch', @search.path
129
+ end
130
+
131
+ should "have multiple indices" do
132
+ @search = Tire::Search::Multi::Search.new ['foo', 'bar']
133
+ assert_equal '/foo,bar/_msearch', @search.path
134
+ end
135
+
136
+ should "have index and type" do
137
+ @search = Tire::Search::Multi::Search.new 'foo', :type => 'bar'
138
+ assert_equal '/foo/bar/_msearch', @search.path
139
+ end
140
+
141
+ should "have index and multiple types" do
142
+ @search = Tire::Search::Multi::Search.new 'foo', :type => ['bar', 'bam']
143
+ assert_equal '/foo/bar,bam/_msearch', @search.path
144
+ end
145
+
146
+ should "contain host" do
147
+ @search = Tire::Search::Multi::Search.new
148
+ assert_equal 'http://localhost:9200/_msearch', @search.url
149
+ end
150
+
151
+ end
152
+
153
+ context "URL params" do
154
+
155
+ should "be empty when no params passed" do
156
+ @search = Tire::Search::Multi::Search.new 'foo'
157
+ assert_equal '', @search.params
158
+ end
159
+
160
+ should "serialize parameters" do
161
+ @search = Tire::Search::Multi::Search.new 'foo', :search_type => 'count'
162
+ assert_equal '?search_type=count', @search.params
163
+ end
164
+
165
+ end
166
+
167
+ context "search request" do
168
+ setup do
169
+ @search = Tire::Search::Multi::Search.new
170
+ end
171
+
172
+ should "have a collection of searches" do
173
+ @search.search :one
174
+ assert_instance_of Tire::Search::Multi::SearchDefinitions, @search.searches
175
+ end
176
+
177
+ should "be initialized with name" do
178
+ @search.search :one
179
+ assert_instance_of Tire::Search::Search, @search.searches[:one]
180
+ end
181
+
182
+ should "be initialized with options" do
183
+ @search.search :foo => 'bar'
184
+ assert_equal 'bar', @search.searches[0].options[:foo]
185
+ end
186
+
187
+ should "be initialized with name and options" do
188
+ @search.search :one, :foo => 'bar'
189
+ assert_instance_of Tire::Search::Search, @search.searches[:one]
190
+ assert_equal 'bar', @search.searches[:one].options[:foo]
191
+ assert_equal 'bar', @search.searches(:one).options[:foo]
192
+ end
193
+
194
+ should "pass the index name and options to the search object" do
195
+ @search.search :index => 'foo', :type => 'bar'
196
+ assert_equal ['foo'], @search.searches[0].indices
197
+ assert_equal ['bar'], @search.searches[0].types
198
+ assert_equal ['foo'], @search.searches(0).indices
199
+ end
200
+
201
+ should "pass options to Search object" do
202
+ @search.search :search_type => 'count'
203
+ assert_equal 'count', @search.searches[0].options[:search_type]
204
+ end
205
+
206
+ end
207
+
208
+ context "payload" do
209
+
210
+ should "serialize search payload header and body as Array" do
211
+ @search = Tire::Search::Multi::Search.new do
212
+ search :index => 'foo' do
213
+ query { all }
214
+ size 100
215
+ end
216
+ end
217
+
218
+ assert_equal 1, @search.searches.size
219
+ assert_equal 2, @search.to_array.size
220
+
221
+ assert_equal( 'foo', @search.to_array[0][:index] )
222
+ assert_equal( {:match_all => {}}, @search.to_array[1][:query] )
223
+ assert_equal( 100, @search.to_array[1][:size] )
224
+ end
225
+
226
+ should "serialize search payload header and body for multiple searches" do
227
+ @search = Tire::Search::Multi::Search.new do
228
+ search(:index => 'foo') { query { all } }
229
+ search(:index => 'ooo') { query { term :foo, 'bar' } }
230
+ end
231
+
232
+ assert_equal 2, @search.searches.size
233
+ assert_equal 4, @search.to_array.size
234
+
235
+ assert_equal 'foo', @search.to_array[0][:index]
236
+ assert_equal 'ooo', @search.to_array[2][:index]
237
+ assert_equal 'match_all', @search.to_array[1][:query].keys.first.to_s
238
+ assert_equal 'term', @search.to_array[3][:query].keys.first.to_s
239
+ end
240
+
241
+ should "serialize search parameters" do
242
+ @search = Tire::Search::Multi::Search.new do
243
+ search(:search_type => 'count') { query { all } }
244
+ end
245
+
246
+ assert_equal 'count', @search.to_array[0][:search_type]
247
+ end
248
+
249
+ should "serialize search payload as a string" do
250
+ @search = Tire::Search::Multi::Search.new do
251
+ search(:index => 'foo') { query { all } }
252
+ end
253
+
254
+ assert_equal 2, @search.to_payload.split("\n").size
255
+ end
256
+
257
+ should "end with a new line" do
258
+ @search = Tire::Search::Multi::Search.new { search(:index => 'foo') { query { all } } }
259
+ assert_match /.*\n$/, @search.to_payload
260
+ end
261
+
262
+ should "leave header empty when no index is passed" do
263
+ @search = Tire::Search::Multi::Search.new do
264
+ search() { query { all } }
265
+ end
266
+
267
+ assert_equal( {}, @search.to_array.first )
268
+ end
269
+
270
+ end
271
+
272
+ context "perform" do
273
+ setup do
274
+ @search = Tire::Search::Multi::Search.new do
275
+ search(:index => 'foo') do
276
+ query { all }
277
+ size 100
278
+ end
279
+ end
280
+ @response = mock_response '{ "responses" : [{"took":1,"hits":{"total":0,"hits":[]}}] }', 200
281
+ end
282
+
283
+ should "perform the request" do
284
+ Configuration.client.expects(:get).
285
+ with do |url, payload|
286
+ assert_equal 'http://localhost:9200/_msearch', url
287
+ assert payload.include?('match_all')
288
+ end.
289
+ returns(@response)
290
+ @search.perform
291
+ end
292
+
293
+ should "log the request" do
294
+ Configuration.client.expects(:get).returns(@response)
295
+ @search.expects(:logged)
296
+ @search.perform
297
+ end
298
+
299
+ end
300
+
301
+ end
302
+
303
+ end
304
+ end