tire-erez 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/.gitignore +14 -0
  2. data/.travis.yml +32 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +10 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.markdown +775 -0
  7. data/Rakefile +51 -0
  8. data/examples/rails-application-template.rb +263 -0
  9. data/examples/tire-dsl.rb +932 -0
  10. data/lib/tire.rb +59 -0
  11. data/lib/tire/alias.rb +296 -0
  12. data/lib/tire/configuration.rb +38 -0
  13. data/lib/tire/count.rb +85 -0
  14. data/lib/tire/dsl.rb +114 -0
  15. data/lib/tire/http/client.rb +62 -0
  16. data/lib/tire/http/clients/curb.rb +61 -0
  17. data/lib/tire/http/clients/faraday.rb +71 -0
  18. data/lib/tire/http/response.rb +27 -0
  19. data/lib/tire/index.rb +443 -0
  20. data/lib/tire/logger.rb +60 -0
  21. data/lib/tire/model/callbacks.rb +40 -0
  22. data/lib/tire/model/import.rb +27 -0
  23. data/lib/tire/model/indexing.rb +134 -0
  24. data/lib/tire/model/naming.rb +100 -0
  25. data/lib/tire/model/percolate.rb +99 -0
  26. data/lib/tire/model/persistence.rb +72 -0
  27. data/lib/tire/model/persistence/attributes.rb +148 -0
  28. data/lib/tire/model/persistence/finders.rb +54 -0
  29. data/lib/tire/model/persistence/storage.rb +77 -0
  30. data/lib/tire/model/search.rb +322 -0
  31. data/lib/tire/multi_search.rb +263 -0
  32. data/lib/tire/results/collection.rb +156 -0
  33. data/lib/tire/results/item.rb +94 -0
  34. data/lib/tire/results/pagination.rb +68 -0
  35. data/lib/tire/rubyext/hash.rb +8 -0
  36. data/lib/tire/rubyext/ruby_1_8.rb +1 -0
  37. data/lib/tire/rubyext/symbol.rb +11 -0
  38. data/lib/tire/rubyext/uri_escape.rb +74 -0
  39. data/lib/tire/search.rb +211 -0
  40. data/lib/tire/search/facet.rb +81 -0
  41. data/lib/tire/search/filter.rb +28 -0
  42. data/lib/tire/search/highlight.rb +37 -0
  43. data/lib/tire/search/queries/match.rb +40 -0
  44. data/lib/tire/search/query.rb +250 -0
  45. data/lib/tire/search/scan.rb +114 -0
  46. data/lib/tire/search/script_field.rb +23 -0
  47. data/lib/tire/search/sort.rb +25 -0
  48. data/lib/tire/tasks.rb +138 -0
  49. data/lib/tire/utils.rb +17 -0
  50. data/lib/tire/version.rb +18 -0
  51. data/test/fixtures/articles/1.json +1 -0
  52. data/test/fixtures/articles/2.json +1 -0
  53. data/test/fixtures/articles/3.json +1 -0
  54. data/test/fixtures/articles/4.json +1 -0
  55. data/test/fixtures/articles/5.json +1 -0
  56. data/test/integration/active_model_indexing_test.rb +51 -0
  57. data/test/integration/active_model_searchable_test.rb +114 -0
  58. data/test/integration/active_record_searchable_test.rb +620 -0
  59. data/test/integration/boolean_queries_test.rb +43 -0
  60. data/test/integration/boosting_queries_test.rb +32 -0
  61. data/test/integration/bulk_test.rb +86 -0
  62. data/test/integration/count_test.rb +64 -0
  63. data/test/integration/custom_score_queries_test.rb +89 -0
  64. data/test/integration/dis_max_queries_test.rb +68 -0
  65. data/test/integration/dsl_search_test.rb +30 -0
  66. data/test/integration/explanation_test.rb +44 -0
  67. data/test/integration/facets_test.rb +311 -0
  68. data/test/integration/filtered_queries_test.rb +66 -0
  69. data/test/integration/filters_test.rb +75 -0
  70. data/test/integration/fuzzy_queries_test.rb +20 -0
  71. data/test/integration/highlight_test.rb +64 -0
  72. data/test/integration/index_aliases_test.rb +122 -0
  73. data/test/integration/index_mapping_test.rb +43 -0
  74. data/test/integration/index_store_test.rb +112 -0
  75. data/test/integration/index_update_document_test.rb +121 -0
  76. data/test/integration/match_query_test.rb +79 -0
  77. data/test/integration/mongoid_searchable_test.rb +309 -0
  78. data/test/integration/multi_search_test.rb +114 -0
  79. data/test/integration/nested_query_test.rb +135 -0
  80. data/test/integration/percolator_test.rb +111 -0
  81. data/test/integration/persistent_model_test.rb +205 -0
  82. data/test/integration/prefix_query_test.rb +43 -0
  83. data/test/integration/query_return_version_test.rb +70 -0
  84. data/test/integration/query_string_test.rb +52 -0
  85. data/test/integration/range_queries_test.rb +36 -0
  86. data/test/integration/reindex_test.rb +56 -0
  87. data/test/integration/results_test.rb +58 -0
  88. data/test/integration/scan_test.rb +56 -0
  89. data/test/integration/script_fields_test.rb +38 -0
  90. data/test/integration/sort_test.rb +52 -0
  91. data/test/integration/text_query_test.rb +39 -0
  92. data/test/models/active_model_article.rb +31 -0
  93. data/test/models/active_model_article_with_callbacks.rb +49 -0
  94. data/test/models/active_model_article_with_custom_document_type.rb +7 -0
  95. data/test/models/active_model_article_with_custom_index_name.rb +7 -0
  96. data/test/models/active_record_models.rb +131 -0
  97. data/test/models/article.rb +15 -0
  98. data/test/models/mongoid_models.rb +85 -0
  99. data/test/models/persistent_article.rb +11 -0
  100. data/test/models/persistent_article_in_index.rb +16 -0
  101. data/test/models/persistent_article_in_namespace.rb +12 -0
  102. data/test/models/persistent_article_with_casting.rb +28 -0
  103. data/test/models/persistent_article_with_defaults.rb +12 -0
  104. data/test/models/persistent_article_with_percolation.rb +5 -0
  105. data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
  106. data/test/models/supermodel_article.rb +17 -0
  107. data/test/models/validated_model.rb +11 -0
  108. data/test/test_helper.rb +118 -0
  109. data/test/unit/active_model_lint_test.rb +17 -0
  110. data/test/unit/configuration_test.rb +84 -0
  111. data/test/unit/count_test.rb +67 -0
  112. data/test/unit/http_client_test.rb +79 -0
  113. data/test/unit/http_response_test.rb +49 -0
  114. data/test/unit/index_alias_test.rb +335 -0
  115. data/test/unit/index_test.rb +1098 -0
  116. data/test/unit/logger_test.rb +125 -0
  117. data/test/unit/model_callbacks_test.rb +116 -0
  118. data/test/unit/model_import_test.rb +75 -0
  119. data/test/unit/model_initialization_test.rb +31 -0
  120. data/test/unit/model_persistence_test.rb +548 -0
  121. data/test/unit/model_search_test.rb +964 -0
  122. data/test/unit/multi_search_test.rb +304 -0
  123. data/test/unit/results_collection_test.rb +372 -0
  124. data/test/unit/results_item_test.rb +173 -0
  125. data/test/unit/rubyext_test.rb +66 -0
  126. data/test/unit/search_facet_test.rb +186 -0
  127. data/test/unit/search_filter_test.rb +42 -0
  128. data/test/unit/search_highlight_test.rb +46 -0
  129. data/test/unit/search_query_test.rb +419 -0
  130. data/test/unit/search_scan_test.rb +113 -0
  131. data/test/unit/search_script_field_test.rb +26 -0
  132. data/test/unit/search_sort_test.rb +50 -0
  133. data/test/unit/search_test.rb +556 -0
  134. data/test/unit/tire_test.rb +144 -0
  135. data/tire.gemspec +83 -0
  136. metadata +586 -0
@@ -0,0 +1,113 @@
1
+ require 'test_helper'
2
+
3
+ module Tire
4
+ module Search
5
+ class ScanTest < Test::Unit::TestCase
6
+
7
+ context "Scan" do
8
+ setup do
9
+ Configuration.reset
10
+ @results = {
11
+ "_scroll_id" => "abc123",
12
+ "took" => 3,
13
+ "hits" => {
14
+ "total" => 10,
15
+ "hits" => [
16
+ { "_id" => "1", "_source" => { "title" => "Test" } }
17
+ ]
18
+ }
19
+ }
20
+ @empty_results = @results.merge('hits' => {'hits' => []})
21
+ @default_response = mock_response @results.to_json, 200
22
+ end
23
+
24
+ should "initialize the search object with the indices" do
25
+ s = Scan.new(['index1', 'index2'])
26
+ assert_instance_of Tire::Search::Search, s.search
27
+ end
28
+
29
+ should "fetch the initial scroll ID" do
30
+ s = Scan.new('index1')
31
+ s.search.expects(:perform).
32
+ returns(stub :json => { '_scroll_id' => 'abc123' })
33
+
34
+ assert_equal 'abc123', s.scroll_id
35
+ end
36
+
37
+ should "perform the request lazily" do
38
+ s = Scan.new('dummy')
39
+
40
+ s.expects(:scroll_id).
41
+ returns('abc123').
42
+ at_least_once
43
+
44
+ Configuration.client.expects(:get).
45
+ with { |url,id| url =~ %r|_search/scroll.*search_type=scan| && id == 'abc123' }.
46
+ returns(@default_response).
47
+ once
48
+
49
+ assert_not_nil s.results
50
+ assert_not_nil s.response
51
+ assert_not_nil s.json
52
+ end
53
+
54
+ should "set the total and seen variables" do
55
+ s = Scan.new('dummy')
56
+ s.expects(:scroll_id).returns('abc123').at_least_once
57
+ Configuration.client.expects(:get).returns(@default_response).at_least_once
58
+
59
+ assert_equal 10, s.total
60
+ assert_equal 1, s.seen
61
+ end
62
+
63
+ should "log the request and response" do
64
+ Tire.configure { logger STDERR }
65
+
66
+ s = Scan.new('dummy')
67
+ s.expects(:scroll_id).returns('abc123').at_least_once
68
+ Configuration.client.expects(:get).returns(@default_response).at_least_once
69
+
70
+ Configuration.logger.expects(:log_request).
71
+ with { |(endpoint, params, curl)| endpoint == 'scroll' }
72
+
73
+ Configuration.logger.expects(:log_response).
74
+ with { |code, took, body| code == 200 && took == 3 && body == '1/10 (10.0%)' }
75
+
76
+ s.__perform
77
+ end
78
+
79
+ context "results" do
80
+ setup do
81
+ @search = Scan.new('dummy')
82
+ @search.expects(:results).
83
+ returns(Results::Collection.new @results).
84
+ then.
85
+ returns(Results::Collection.new @empty_results).
86
+ at_least_once
87
+ @search.results
88
+ end
89
+
90
+ should "be iterable" do
91
+ assert_respond_to @search, :each
92
+ assert_respond_to @search, :size
93
+
94
+ assert_nothing_raised do
95
+ @search.each { |batch| p batch; assert_equal 'Test', batch.first.title }
96
+ end
97
+ end
98
+
99
+ should "be iterable by individual documents" do
100
+ assert_respond_to @search, :each_document
101
+
102
+ assert_nothing_raised do
103
+ @search.each_document { |item| assert_equal 'Test', item.title }
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,26 @@
1
+ require 'test_helper'
2
+
3
+ module Tire::Search
4
+
5
+ class ScriptFieldTest < Test::Unit::TestCase
6
+
7
+ context "ScriptField" do
8
+
9
+ should "be serialized to JSON" do
10
+ assert_respond_to ScriptField.new(:test1, {}), :to_json
11
+ end
12
+
13
+ should "encode simple declarations as JSON" do
14
+ assert_equal( { :test1 => { :script => "doc['my_field_name'].value * factor",
15
+ :params => { :factor => 2.2 }, :lang => :js } }.to_json,
16
+
17
+ ScriptField.new( :test1,
18
+ { :script => "doc['my_field_name'].value * factor",
19
+ :params => { :factor => 2.2 }, :lang => :js } ).to_json
20
+ )
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,50 @@
1
+ require 'test_helper'
2
+
3
+ module Tire::Search
4
+
5
+ class SortTest < Test::Unit::TestCase
6
+
7
+ context "Sort" do
8
+
9
+ should "be serialized to JSON" do
10
+ assert_respond_to Sort.new, :to_json
11
+ end
12
+
13
+ should "encode simple strings" do
14
+ assert_equal [:foo].to_json, Sort.new.by(:foo).to_json
15
+ end
16
+
17
+ should "encode method arguments" do
18
+ assert_equal [:foo => 'desc'].to_json, Sort.new.by(:foo, 'desc').to_json
19
+ end
20
+
21
+ should "encode hash" do
22
+ assert_equal [ :foo => { :reverse => true } ].to_json, Sort.new.by(:foo, :reverse => true).to_json
23
+ end
24
+
25
+ should "encode multiple sort fields in chain" do
26
+ assert_equal [:foo, :bar].to_json, Sort.new.by(:foo).by(:bar).to_json
27
+ end
28
+
29
+ should "encode fields when passed as a block to constructor" do
30
+ s = Sort.new do
31
+ by :foo
32
+ by :bar, 'desc'
33
+ by :_score
34
+ end
35
+ assert_equal [ :foo, {:bar => 'desc'}, :_score ].to_json, s.to_json
36
+ end
37
+
38
+ should "encode fields deeper in json" do
39
+ s = Sort.new { by 'author.name' }
40
+ assert_equal [ 'author.name' ].to_json, s.to_json
41
+
42
+ s = Sort.new { by 'author.name', 'desc' }
43
+ assert_equal [ {'author.name' => 'desc'} ].to_json, s.to_json
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,556 @@
1
+ require 'test_helper'
2
+
3
+ module Tire
4
+
5
+ class SearchTest < Test::Unit::TestCase
6
+
7
+ context "Search" do
8
+ setup { Configuration.reset }
9
+
10
+ should "be initialized with single index" do
11
+ s = Search::Search.new('index') { query { string 'foo' } }
12
+ assert_match %r|/index/_search|, s.url
13
+ end
14
+
15
+ should "be initialized with multiple indices" do
16
+ s = Search::Search.new(['index1','index2']) { query { string 'foo' } }
17
+ assert_match %r|/index1,index2/_search|, s.url
18
+ end
19
+
20
+ should "be initialized with multiple indices with options" do
21
+ indices = {'index1' => {:boost => 1},'index2' => {:boost => 2}}
22
+ s = Search::Search.new(indices) { query { string 'foo' } }
23
+ assert_match /index1/, s.url
24
+ assert_match /index2/, s.url
25
+ assert_equal({'index1' => 1, 'index2' => 2}, s.to_hash[:indices_boost])
26
+ end
27
+
28
+ should "be initialized with multiple indices as string" do
29
+ s = Search::Search.new(['index1,index2,index3']) { query { string 'foo' } }
30
+ assert_match %r|/index1,index2,index3/_search|, s.url
31
+ end
32
+
33
+ should "allow to search all indices by leaving index empty" do
34
+ s = Search::Search.new { query { string 'foo' } }
35
+ assert_match %r|localhost:9200/_search|, s.url
36
+ end
37
+
38
+ should "allow to limit results with document type" do
39
+ s = Search::Search.new('index', :type => 'bar') do
40
+ query { string 'foo' }
41
+ end
42
+
43
+ assert_match %r|index/bar/_search|, s.url
44
+ end
45
+
46
+ should "allow to pass search parameters" do
47
+ s = Search::Search.new('index', :routing => 123, :timeout => 1) { query { string 'foo' } }
48
+
49
+ assert ! s.params.empty?
50
+
51
+ assert_match %r|routing=123|, s.params
52
+ assert_match %r|timeout=1|, s.params
53
+ end
54
+
55
+ should "encode search parameters in the request" do
56
+ Configuration.client.expects(:get).with do |url, payload|
57
+ url.include? 'routing=123&timeout=1'
58
+ end.returns mock_response( { 'hits' => { 'hits' => [ {:_id => 1} ] } }.to_json )
59
+
60
+ Search::Search.new('index', :routing => 123, :timeout => 1) { query { string 'foo' } }.perform
61
+ end
62
+
63
+ should "encode missing params as an empty string" do
64
+ Configuration.client.expects(:get).with do |url, payload|
65
+ (! url.include? '?') && (! url.include? '&')
66
+ end.returns mock_response( { 'hits' => { 'hits' => [ {:_id => 1} ] } }.to_json )
67
+
68
+ s = Search::Search.new('index') { query { string 'foo' } }
69
+ s.perform
70
+
71
+ assert_equal '', s.params
72
+ end
73
+
74
+ should "properly encode namespaced document type" do
75
+ Configuration.client.expects(:get).with do |url, payload|
76
+ url.match %r|index/my_application%2Farticle/_search|
77
+ end.returns mock_response( { 'hits' => { 'hits' => [ {:_id => 1} ] } }.to_json )
78
+
79
+ s = Search::Search.new('index', :type => 'my_application/article') do
80
+ query { string 'foo' }
81
+ end
82
+ s.perform
83
+
84
+ assert_match %r|index/my_application%2Farticle/_search|, s.url
85
+ assert_match %r|index/my_application%2Farticle/_search|, s.to_curl
86
+ end
87
+
88
+ should "allow to pass block to query" do
89
+ Search::Query.any_instance.expects(:instance_eval)
90
+
91
+ Search::Search.new('index') do
92
+ query { string 'foo' }
93
+ end
94
+ end
95
+
96
+ should "allow to pass block with argument to query (use variables from outer scope)" do
97
+ def foo; 'bar'; end
98
+
99
+ Search::Query.any_instance.expects(:instance_eval).never
100
+
101
+ Search::Search.new('index') do |search|
102
+ search.query do |query|
103
+ query.string foo
104
+ end
105
+ end
106
+ end
107
+
108
+ should "store indices as an array" do
109
+ s = Search::Search.new('index1') do;end
110
+ assert_equal ['index1'], s.indices
111
+
112
+ s = Search::Search.new(['index1', 'index2']) do;end
113
+ assert_equal ['index1', 'index2'], s.indices
114
+ end
115
+
116
+ should "return curl snippet for debugging" do
117
+ s = Search::Search.new('index') do
118
+ query { string 'title:foo' }
119
+ end
120
+ assert_match %r|curl \-X GET 'http://localhost:9200/index/_search\?pretty' -d |, s.to_curl
121
+ assert_match %r|\s*{\s*"query"\s*:\s*"title:foo"\s*}\s*|, s.to_curl
122
+ end
123
+
124
+ should "return curl snippet with multiple indices for debugging" do
125
+ s = Search::Search.new(['index_1', 'index_2']) do
126
+ query { string 'title:foo' }
127
+ end
128
+ assert_match /index_1,index_2/, s.to_curl
129
+ end
130
+
131
+ should "return itself as a Hash" do
132
+ s = Search::Search.new('index') do
133
+ query { string 'title:foo' }
134
+ end
135
+ assert_nothing_raised do
136
+ assert_instance_of Hash, s.to_hash
137
+ assert_equal "title:foo", s.to_hash[:query][:query_string][:query]
138
+ end
139
+ end
140
+
141
+ should "allow chaining" do
142
+ assert_nothing_raised do
143
+ Search::Search.new('index').query { }.
144
+ sort { by :title, 'desc' }.
145
+ size(5).
146
+ sort { by :name, 'asc' }.
147
+ from(1).
148
+ version(true)
149
+ end
150
+ end
151
+
152
+ should "perform the search lazily" do
153
+ response = mock_response '{"took":1,"hits":[]}', 200
154
+ Configuration.client.expects(:get).returns(response)
155
+ Results::Collection.expects(:new).returns([])
156
+
157
+ s = Search::Search.new('index')
158
+ assert_not_nil s.results
159
+ assert_not_nil s.response
160
+ assert_not_nil s.json
161
+ end
162
+
163
+ should "allow the search criteria to be chained" do
164
+ s = Search::Search.new('index').query { string 'foo' }
165
+ assert_nil s.filters, "Should NOT have filters"
166
+
167
+ s.expects(:perform).once
168
+ s.filter :term, :other_field => 'bar'
169
+ assert s.filters.size == 1, "Should have filters"
170
+ s.results
171
+ end
172
+
173
+ should "print debugging information on exception and return false" do
174
+ ::RestClient::Request.any_instance.
175
+ expects(:execute).
176
+ raises(::RestClient::InternalServerError)
177
+ STDERR.expects(:puts)
178
+
179
+ s = Search::Search.new('index')
180
+ assert_raise Search::SearchRequestFailed do
181
+ s.perform
182
+ end
183
+ end
184
+
185
+ should "log request, but not response, when logger is set" do
186
+ Configuration.logger STDERR
187
+
188
+ Configuration.client.expects(:get).returns(mock_response( '{"took":1,"hits":[]}', 200 ))
189
+
190
+ Results::Collection.expects(:new).returns([])
191
+ Configuration.logger.expects(:log_request).returns(true)
192
+ Configuration.logger.expects(:log_response).with(200, 1, '')
193
+
194
+ Search::Search.new('index').perform
195
+ end
196
+
197
+ should "log the original exception on failed request" do
198
+ Configuration.logger STDERR
199
+
200
+ Configuration.client.expects(:get).raises(Errno::ECONNREFUSED)
201
+ Configuration.logger.expects(:log_response).with('N/A', 'N/A', '')
202
+
203
+ assert_raise Errno::ECONNREFUSED do
204
+ Search::Search.new('index').perform
205
+ end
206
+ end
207
+
208
+ should "allow to set the server url" do
209
+ search = Search::Search.new('indexA')
210
+ Configuration.url 'http://es1.example.com'
211
+
212
+ Configuration.client.
213
+ expects(:get).
214
+ with do |url, payload|
215
+ url == 'http://es1.example.com/indexA/_search'
216
+ end.
217
+ returns(mock_response( '{"took":1,"hits":{"total": 0, "hits" : []}}', 200 ))
218
+
219
+ search.perform
220
+ end
221
+
222
+ context "sort" do
223
+
224
+ should "allow sorting by multiple fields" do
225
+ s = Search::Search.new('index') do
226
+ sort do
227
+ by :title, 'desc'
228
+ by :_score
229
+ end
230
+ end
231
+ hash = MultiJson.decode( s.to_json )
232
+ assert_equal [{'title' => 'desc'}, '_score'], hash['sort']
233
+ end
234
+
235
+ should "allow to track scores" do
236
+ s = Search::Search.new('index') do
237
+ sort { by :title }
238
+ track_scores true
239
+ end
240
+
241
+ assert_equal 'true', s.to_hash[:track_scores].to_json
242
+ end
243
+
244
+ end
245
+
246
+ context "facets" do
247
+
248
+ should "retrieve terms facets" do
249
+ s = Search::Search.new('index') do
250
+ facet('foo1') { terms :bar, :global => true }
251
+ facet('foo2', :global => true) { terms :bar }
252
+ facet('foo3') { terms :baz }
253
+ end
254
+ assert_equal 3, s.facets.keys.size
255
+ assert_not_nil s.facets['foo1']
256
+ assert_not_nil s.facets['foo2']
257
+ assert_not_nil s.facets['foo3']
258
+ end
259
+
260
+ should "retrieve date histogram facets" do
261
+ s = Search::Search.new('index') do
262
+ facet('date') { date :published_on }
263
+ end
264
+ assert_equal 1, s.facets.keys.size
265
+ assert_not_nil s.facets['date']
266
+ end
267
+
268
+ end
269
+
270
+ context "filter" do
271
+
272
+ should "allow to specify filter" do
273
+ s = Search::Search.new('index') do
274
+ filter :terms, :tags => ['foo']
275
+ end
276
+
277
+ assert_equal 1, s.filters.size
278
+
279
+ assert_not_nil s.filters.first
280
+ assert_not_nil s.filters.first[:terms]
281
+
282
+ assert_equal( {:terms => {:tags => ['foo']}}.to_json,
283
+ s.to_hash[:filter].to_json )
284
+ end
285
+
286
+ should "allow to add multiple filters" do
287
+ s = Search::Search.new('index') do
288
+ filter :terms, :tags => ['foo']
289
+ filter :term, :words => 125
290
+ end
291
+
292
+ assert_equal 2, s.filters.size
293
+
294
+ assert_not_nil s.filters.first[:terms]
295
+ assert_not_nil s.filters.last[:term]
296
+
297
+ assert_equal( { :and => [ {:terms => {:tags => ['foo']}}, {:term => {:words => 125}} ] }.to_json,
298
+ s.to_hash[:filter].to_json )
299
+ end
300
+
301
+ end
302
+
303
+ context "highlight" do
304
+
305
+ should "allow to specify highlight for single field" do
306
+ s = Search::Search.new('index') do
307
+ highlight :body
308
+ end
309
+
310
+ assert_not_nil s.highlight
311
+ assert_instance_of Tire::Search::Highlight, s.highlight
312
+ end
313
+
314
+ should "allow to specify highlight for more fields" do
315
+ s = Search::Search.new('index') do
316
+ highlight :body, :title
317
+ end
318
+
319
+ assert_not_nil s.highlight
320
+ assert_instance_of Tire::Search::Highlight, s.highlight
321
+ end
322
+
323
+ should "allow to specify highlight with for more fields with options" do
324
+ s = Search::Search.new('index') do
325
+ highlight :body, :title => { :fragment_size => 150, :number_of_fragments => 3 }
326
+ end
327
+
328
+ assert_not_nil s.highlight
329
+ assert_instance_of Tire::Search::Highlight, s.highlight
330
+ end
331
+
332
+ end
333
+
334
+ context "with version" do
335
+
336
+ should "set the version" do
337
+ s = Search::Search.new('index') do
338
+ version true
339
+ end
340
+ hash = MultiJson.decode( s.to_json )
341
+ assert_equal true, hash['version']
342
+ end
343
+
344
+ end
345
+
346
+ context "with from/size" do
347
+
348
+ should "set the values in request" do
349
+ s = Search::Search.new('index') do
350
+ size 5
351
+ from 3
352
+ end
353
+ hash = MultiJson.decode( s.to_json )
354
+ assert_equal 5, hash['size']
355
+ assert_equal 3, hash['from']
356
+ end
357
+
358
+ should "set the size value in options" do
359
+ Results::Collection.any_instance.stubs(:total).returns(50)
360
+ s = Search::Search.new('index') do
361
+ size 5
362
+ end
363
+
364
+ assert_equal 5, s.options[:size]
365
+ end
366
+
367
+ should "set the from value in options" do
368
+ Results::Collection.any_instance.stubs(:total).returns(50)
369
+ s = Search::Search.new('index') do
370
+ from 5
371
+ end
372
+
373
+ assert_equal 5, s.options[:from]
374
+ end
375
+
376
+ end
377
+
378
+ context "when limiting returned fields" do
379
+
380
+ should "set the fields limit in request" do
381
+ s = Search::Search.new('index') do
382
+ fields :title
383
+ end
384
+ hash = MultiJson.decode( s.to_json )
385
+ assert_equal ['title'], hash['fields']
386
+ end
387
+
388
+ should "take multiple fields as an Array" do
389
+ s = Search::Search.new('index') do
390
+ fields [:title, :tags]
391
+ end
392
+ hash = MultiJson.decode( s.to_json )
393
+ assert_equal ['title', 'tags'], hash['fields']
394
+ end
395
+
396
+ should "take multiple fields as splat argument" do
397
+ s = Search::Search.new('index') do
398
+ fields :title, :tags
399
+ end
400
+ hash = MultiJson.decode( s.to_json )
401
+ assert_equal ['title', 'tags'], hash['fields']
402
+ end
403
+
404
+ end
405
+
406
+ context "with min_score" do
407
+ should "allow to specify minimum score for returned documents" do
408
+ s = Search::Search.new('index') do
409
+ query { string 'foo' }
410
+ min_score 0.5
411
+ end
412
+
413
+ assert_equal( '0.5', s.to_hash[:min_score].to_json )
414
+ end
415
+ end
416
+
417
+ context "with partial fields" do
418
+
419
+ should "add partial_fields config" do
420
+ s = Search::Search.new('index') do
421
+ partial_field 'name', :include => 'name_*'
422
+ end
423
+
424
+ hash = MultiJson.decode( s.to_json )
425
+ assert_equal({'name' => { 'include' => 'name_*'} }, hash['partial_fields'])
426
+ end
427
+
428
+ end
429
+
430
+ context "explain" do
431
+
432
+ should "default to false" do
433
+ s = Search::Search.new('index') do
434
+ end
435
+ hash = MultiJson.decode( s.to_json )
436
+ assert_nil hash['explain']
437
+ end
438
+
439
+ should "set the explain field in the request when true" do
440
+ s = Search::Search.new('index') do
441
+ explain true
442
+ end
443
+ hash = MultiJson.decode( s.to_json )
444
+ assert_equal true, hash['explain']
445
+ end
446
+
447
+ should "not set the explain field when false" do
448
+ s = Search::Search.new('index') do
449
+ explain false
450
+ end
451
+ hash = MultiJson.decode( s.to_json )
452
+ assert_nil hash['explain']
453
+ end
454
+
455
+ end
456
+
457
+ context "boolean queries" do
458
+
459
+ should "wrap other queries" do
460
+ # TODO: Try to get rid of the `boolean` method
461
+ #
462
+ # TODO: Try to get rid of multiple `should`, `must`, invocations, and wrap queries like this:
463
+ # boolean do
464
+ # should do
465
+ # string 'foo'
466
+ # string 'bar'
467
+ # end
468
+ # end
469
+ s = Search::Search.new('index') do
470
+ query do
471
+ boolean do
472
+ should { string 'foo' }
473
+ should { string 'moo' }
474
+ must { string 'title:bar' }
475
+ must { terms :tags, ['baz'] }
476
+ end
477
+ end
478
+ end
479
+
480
+ hash = MultiJson.decode(s.to_json)
481
+ query = hash['query']['bool']
482
+ # p hash
483
+
484
+ assert_equal 2, query['should'].size
485
+ assert_equal 2, query['must'].size
486
+
487
+ assert_equal( { 'query_string' => { 'query' => 'foo' } }, query['should'].first)
488
+ assert_equal( { 'terms' => { 'tags' => ['baz'] } }, query['must'].last)
489
+ end
490
+
491
+ end
492
+
493
+ context "boosting queries" do
494
+
495
+ should "wrap other queries" do
496
+ s = Search::Search.new('index') do
497
+ query do
498
+ boosting do
499
+ positive { string 'foo' }
500
+ positive { term('bar', 'baz') }
501
+ negative { term('bar', 'moo') }
502
+ end
503
+ end
504
+ end
505
+
506
+ hash = MultiJson.decode(s.to_json)
507
+ query = hash['query']['boosting']
508
+
509
+ assert_equal 2, query['positive'].size
510
+ assert_equal 1, query['negative'].size
511
+
512
+ assert_equal( { 'query_string' => { 'query' => 'foo' } }, query['positive'].first)
513
+ assert_equal( { 'term' => { 'bar' => {'term' => 'moo' } } }, query['negative'].first)
514
+ end
515
+
516
+ end
517
+
518
+ end
519
+
520
+ context "script field" do
521
+
522
+ should "allow to specify script field" do
523
+ s = Search::Search.new('index') do
524
+ script_field :test1, :script => "doc['my_field_name'].value * 2"
525
+ end
526
+
527
+ assert_equal 1, s.script_fields.size
528
+
529
+ assert_not_nil s.script_fields
530
+ assert_not_nil s.script_fields[:test1]
531
+
532
+ assert_equal( {:test1 => { :script => "doc['my_field_name'].value * 2" }}.to_json,
533
+ s.to_hash[:script_fields].to_json )
534
+ end
535
+
536
+ should "allow to add multiple script fields" do
537
+ s = Search::Search.new('index') do
538
+ script_field :field1, :script => "doc['my_field_name'].value * 2"
539
+ script_field :field2, :script => "doc['other_field_name'].value * 3"
540
+ end
541
+
542
+ assert_equal 2, s.script_fields.size
543
+
544
+ assert_not_nil s.script_fields[:field1]
545
+ assert_not_nil s.script_fields[:field2]
546
+
547
+ assert_equal( { :field1 => { :script => "doc['my_field_name'].value * 2" }, :field2 => { :script => "doc['other_field_name'].value * 3" } }.to_json,
548
+ s.to_hash[:script_fields].to_json )
549
+ end
550
+
551
+ end
552
+
553
+
554
+ end
555
+
556
+ end