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,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
@@ -0,0 +1,372 @@
1
+ require 'test_helper'
2
+
3
+ module Tire
4
+
5
+ class ResultsCollectionTest < Test::Unit::TestCase
6
+
7
+ context "Collection" do
8
+ setup do
9
+ begin; Object.send(:remove_const, :Rails); rescue; end
10
+ Configuration.reset
11
+ @default_response = { 'hits' => { 'hits' => [{'_id' => 1, '_score' => 1, '_source' => {:title => 'Test', :author => 'John'}},
12
+ {'_id' => 2},
13
+ {'_id' => 3}],
14
+ 'max_score' => 1.0 } }
15
+ end
16
+
17
+ should "be iterable" do
18
+ assert_respond_to Results::Collection.new(@default_response), :each
19
+ assert_respond_to Results::Collection.new(@default_response), :size
20
+ assert_nothing_raised do
21
+ Results::Collection.new(@default_response).each { |item| item.id + 1 }
22
+ Results::Collection.new(@default_response).map { |item| item.id + 1 }
23
+ end
24
+ end
25
+
26
+ should "have size/length" do
27
+ assert_equal 3, Results::Collection.new(@default_response).size
28
+ assert_equal 3, Results::Collection.new(@default_response).length
29
+ end
30
+
31
+ should "allow access to items" do
32
+ assert_not_nil Results::Collection.new(@default_response)[1]
33
+ assert_equal 2, Results::Collection.new(@default_response)[1][:id]
34
+ end
35
+
36
+ should "allow slicing" do
37
+ assert_equal [2,3], Results::Collection.new(@default_response)[1,2].map {|res| res[:id]}
38
+ assert_equal [3], Results::Collection.new(@default_response)[-1,1].map {|res| res[:id]}
39
+ end
40
+
41
+ should "be initialized with parsed JSON" do
42
+ assert_nothing_raised do
43
+ collection = Results::Collection.new( @default_response )
44
+ assert_equal 3, collection.results.count
45
+ end
46
+ end
47
+
48
+ should "return success/failure state" do
49
+ assert Results::Collection.new( @default_response ).success?
50
+ end
51
+
52
+ should "be populated lazily" do
53
+ collection = Results::Collection.new(@default_response)
54
+ assert_nil collection.instance_variable_get(:@results)
55
+ end
56
+
57
+ should "store passed options" do
58
+ collection = Results::Collection.new( @default_response, :per_page => 20, :page => 2 )
59
+ assert_equal 20, collection.options[:per_page]
60
+ assert_equal 2, collection.options[:page]
61
+ end
62
+
63
+ should "be will_paginate compatible" do
64
+ collection = Results::Collection.new(@default_response)
65
+ %w(total_pages offset current_page per_page total_entries).each do |method|
66
+ assert_respond_to collection, method
67
+ end
68
+ end
69
+
70
+ should "be kaminari compatible" do
71
+ collection = Results::Collection.new(@default_response)
72
+ %w(limit_value total_count num_pages offset_value first_page? last_page?).each do |method|
73
+ assert_respond_to collection, method
74
+ end
75
+ end
76
+
77
+ should "have max_score" do
78
+ collection = Results::Collection.new(@default_response)
79
+ assert_equal 1.0, collection.max_score
80
+ end
81
+
82
+ context "serialization" do
83
+
84
+ should "be serialized to JSON" do
85
+ collection = Results::Collection.new(@default_response)
86
+ assert_instance_of Array, collection.as_json
87
+ assert_equal 'Test', collection.as_json.first['title']
88
+ assert_equal 'John', collection.as_json.first['author']
89
+ end
90
+
91
+ should "pass options to as_json" do
92
+ collection = Results::Collection.new(@default_response)
93
+ assert_equal 'Test', collection.as_json(:only => 'title').first['title']
94
+ assert_nil collection.as_json(:only => 'title').first['author']
95
+ end
96
+
97
+ end
98
+
99
+ context "with error response" do
100
+ setup do
101
+ @collection = Results::Collection.new({'error' => 'SearchPhaseExecutionException...'})
102
+ end
103
+
104
+ should "return the error" do
105
+ assert_equal 'SearchPhaseExecutionException...', @collection.error
106
+ end
107
+
108
+ should "return the success/failure state" do
109
+ assert @collection.failure?
110
+ end
111
+
112
+ should "return empty results" do
113
+ assert @collection.empty?
114
+ end
115
+ end
116
+
117
+ context "wrapping results" do
118
+
119
+ setup do
120
+ @response = { 'hits' => { 'hits' => [ { '_id' => 1, '_score' => 0.5, '_index' => 'testing', '_type' => 'article', '_source' => { :title => 'Test', :body => 'Lorem' } } ] } }
121
+ end
122
+
123
+ should "wrap hits in Item by default" do
124
+ document = Results::Collection.new(@response).first
125
+ assert_kind_of Results::Item, document
126
+ assert_equal 'Test', document.title
127
+ end
128
+
129
+ should "NOT allow access to raw underlying Hash in Item" do
130
+ document = Results::Collection.new(@response).first
131
+ assert_nil document[:_source]
132
+ assert_nil document['_source']
133
+ end
134
+
135
+ should "allow wrapping hits in a Hash" do
136
+ Configuration.wrapper(Hash)
137
+
138
+ document = Results::Collection.new(@response).first
139
+ assert_kind_of Hash, document
140
+ assert_raise(NoMethodError) { document.title }
141
+ assert_equal 'Test', document['_source'][:title]
142
+ end
143
+
144
+ should "allow wrapping hits in custom class" do
145
+ Configuration.wrapper(Article)
146
+
147
+ article = Results::Collection.new(@response).first
148
+ assert_kind_of Article, article
149
+ assert_equal 'Test', article.title
150
+ end
151
+
152
+ should "return score" do
153
+ document = Results::Collection.new(@response).first
154
+ assert_equal 0.5, document._score
155
+ end
156
+
157
+ should "return id" do
158
+ document = Results::Collection.new(@response).first
159
+ assert_equal 1, document.id
160
+ end
161
+
162
+ should "return index" do
163
+ document = Results::Collection.new(@response).first
164
+ assert_equal "testing", document._index
165
+ end
166
+
167
+ should "return type" do
168
+ document = Results::Collection.new(@response).first
169
+ assert_equal "article", document._type
170
+ end
171
+
172
+ should "properly decode type" do
173
+ @response = { 'hits' => { 'hits' => [ { '_id' => 1, '_type' => 'foo%2Fbar' } ] } }
174
+ document = Results::Collection.new(@response).first
175
+ assert_equal "foo/bar", document._type
176
+ end
177
+
178
+ end
179
+
180
+ context "wrapping results with selected fields" do
181
+ # When limiting fields from _source to return ES returns them prefixed, not as "real" Hashes.
182
+ # Underlying issue: https://github.com/karmi/tire/pull/31#issuecomment-1340967
183
+ #
184
+ setup do
185
+ Configuration.reset
186
+ @default_response = { 'hits' => { 'hits' =>
187
+ [ { '_id' => 1, '_score' => 0.5, '_index' => 'testing', '_type' => 'article',
188
+ 'fields' => {
189
+ 'title' => 'Knee Deep in JSON',
190
+ 'crazy.field' => 'CRAAAAZY!',
191
+ '_source.artist' => {
192
+ 'name' => 'Elastiq',
193
+ 'meta' => {
194
+ 'favorited' => 1000,
195
+ 'url' => 'http://first.fm/abc123/xyz567'
196
+ }
197
+ },
198
+ '_source.track.info.duration' => {
199
+ 'minutes' => 3
200
+ }
201
+ } } ] } }
202
+ collection = Results::Collection.new(@default_response)
203
+ @item = collection.first
204
+ end
205
+
206
+ should "return fields from the first level" do
207
+ assert_equal 'Knee Deep in JSON', @item.title
208
+ end
209
+
210
+ should "return fields from the _source prefixed and nested fields" do
211
+ assert_equal 'Elastiq', @item.artist.name
212
+ assert_equal 1000, @item.artist.meta.favorited
213
+ assert_equal 3, @item.track.info.duration.minutes
214
+ end
215
+
216
+ end
217
+
218
+ context "returning results with hits" do
219
+ should "yield the Item result and the raw hit" do
220
+ response = { 'hits' => { 'hits' => [ { '_id' => 1, '_score' => 0.5, '_index' => 'testing', '_type' => 'article', '_source' => { :title => 'Test', :body => 'Lorem' } } ] } }
221
+
222
+ Results::Collection.new(response).each_with_hit do |result, hit|
223
+ assert_instance_of Tire::Results::Item, result
224
+ assert_instance_of Hash, hit
225
+ assert_equal 'Test', result.title
226
+ assert_equal 0.5, hit['_score']
227
+ end
228
+ end
229
+
230
+ should "yield the model instance and the raw hit" do
231
+ response = { 'hits' => { 'hits' => [ {'_id' => 1, '_score' => 0.5, '_type' => 'active_record_article'} ] } }
232
+
233
+ ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" )
234
+ ActiveRecord::Migration.verbose = false
235
+ ActiveRecord::Schema.define(:version => 1) { create_table(:active_record_articles) { |t| t.string(:title) } }
236
+ model = ActiveRecordArticle.new(:title => 'Test'); model.id = 1
237
+
238
+ ActiveRecordArticle.expects(:find).with([1]).returns([ model] )
239
+
240
+ Results::Collection.new(response, :load => true).each_with_hit do |result, hit|
241
+ assert_instance_of ActiveRecordArticle, result
242
+ assert_instance_of Hash, hit
243
+ assert_equal 'Test', result.title
244
+ assert_equal 0.5, hit['_score']
245
+ end
246
+
247
+ end
248
+
249
+
250
+ end
251
+
252
+ context "while paginating results" do
253
+
254
+ setup do
255
+ @default_response = { 'hits' => { 'hits' => [{'_id' => 1, '_score' => 1, '_source' => {:title => 'Test'}},
256
+ {'_id' => 2},
257
+ {'_id' => 3},
258
+ {'_id' => 4}],
259
+ 'total' => 4 },
260
+ 'took' => 1 }
261
+ @collection = Results::Collection.new( @default_response, :per_page => 1, :page => 2 )
262
+ end
263
+
264
+ should "return total entries" do
265
+ assert_equal 4, @collection.total
266
+ assert_equal 4, @collection.total_entries
267
+ end
268
+
269
+ should "return total pages" do
270
+ assert_equal 4, @collection.total_pages
271
+ @collection = Results::Collection.new( @default_response, :per_page => 2, :page => 2 )
272
+ assert_equal 2, @collection.total_pages
273
+ @collection = Results::Collection.new( @default_response, :per_page => 3, :page => 2 )
274
+ assert_equal 2, @collection.total_pages
275
+ end
276
+
277
+ should "return total pages when per_page option not set" do
278
+ collection = Results::Collection.new( @default_response, :page => 1 )
279
+ assert_equal 1, collection.total_pages
280
+ end
281
+
282
+ should "return current page" do
283
+ assert_equal 2, @collection.current_page
284
+ end
285
+
286
+ should "return current page for empty result" do
287
+ collection = Results::Collection.new( { 'hits' => { 'hits' => [], 'total' => 0 } } )
288
+ assert_equal 1, collection.current_page
289
+ end
290
+
291
+ should "return previous page" do
292
+ assert_equal 1, @collection.previous_page
293
+ end
294
+
295
+ should "return next page" do
296
+ assert_equal 3, @collection.next_page
297
+ end
298
+
299
+ should "have default per_page" do
300
+ assert_equal 10, Tire::Results::Pagination::default_per_page
301
+
302
+ collection = Results::Collection.new @default_response
303
+ assert_equal 10, collection.per_page
304
+ end
305
+
306
+ end
307
+
308
+ context "with eager loading" do
309
+ setup do
310
+ @response = { 'hits' => { 'hits' => [ {'_id' => 1, '_type' => 'active_record_article'},
311
+ {'_id' => 2, '_type' => 'active_record_article'},
312
+ {'_id' => 3, '_type' => 'active_record_article'}] } }
313
+ ActiveRecordArticle.stubs(:inspect).returns("<ActiveRecordArticle>")
314
+ end
315
+
316
+ should "load the records via model find method from database" do
317
+ ActiveRecordArticle.expects(:find).with([1,2,3]).
318
+ returns([ Results::Item.new(:id => 3),
319
+ Results::Item.new(:id => 1),
320
+ Results::Item.new(:id => 2) ])
321
+ Results::Collection.new(@response, :load => true).results
322
+ end
323
+
324
+ should "pass the :load option Hash to model find metod" do
325
+ ActiveRecordArticle.expects(:find).with([1,2,3], :include => 'comments').
326
+ returns([ Results::Item.new(:id => 3),
327
+ Results::Item.new(:id => 1),
328
+ Results::Item.new(:id => 2) ])
329
+ Results::Collection.new(@response, :load => { :include => 'comments' }).results
330
+ end
331
+
332
+ should "preserve the order of records returned from search" do
333
+ ActiveRecordArticle.expects(:find).with([1,2,3]).
334
+ returns([ Results::Item.new(:id => 3),
335
+ Results::Item.new(:id => 1),
336
+ Results::Item.new(:id => 2) ])
337
+ assert_equal [1,2,3], Results::Collection.new(@response, :load => true).results.map(&:id)
338
+ end
339
+
340
+ should "raise error when model class cannot be inferred from _type" do
341
+ assert_raise(NameError) do
342
+ response = { 'hits' => { 'hits' => [ {'_id' => 1, '_type' => 'hic_sunt_leones'}] } }
343
+ Results::Collection.new(response, :load => true).results
344
+ end
345
+ end
346
+
347
+ should "raise error when _type is missing" do
348
+ assert_raise(NoMethodError) do
349
+ response = { 'hits' => { 'hits' => [ {'_id' => 1}] } }
350
+ Results::Collection.new(response, :load => true).results
351
+ end
352
+ end
353
+
354
+ should "return empty array for empty hits" do
355
+ response = { 'hits' => {
356
+ 'hits' => [],
357
+ 'total' => 4
358
+ },
359
+ 'took' => 1 }
360
+ @collection = Results::Collection.new( response, :load => true )
361
+ assert @collection.empty?, 'Collection should be empty'
362
+ assert @collection.results.empty?, 'Collection results should be empty'
363
+ assert_equal 0, @collection.size
364
+ end
365
+
366
+ end
367
+
368
+ end
369
+
370
+ end
371
+
372
+ end