tire 0.4.3 → 0.5.0

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.
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
data/lib/tire/version.rb CHANGED
@@ -1,15 +1,33 @@
1
1
  module Tire
2
- VERSION = "0.4.3"
2
+ VERSION = "0.5.0"
3
3
 
4
4
  CHANGELOG =<<-END
5
5
  IMPORTANT CHANGES LATELY:
6
6
 
7
- * Added a prefix query
8
- * Added support for "script fields" (return a script evaluation for each hit)
9
- * Added support for the Update API in Tire::Index
10
- * [FIX] Fixed incorrect `Results::Item#to_hash` serialization
11
- * 730813f Added support for aggregating over multiple fields in the "terms" facet
12
- * Added the "Dis Max Query"
13
- * Added the ability to transform documents when reindexing
7
+ * [!BREAKING!] Change format of sort/order in simple model searches to <field>:<direction>
8
+ * [FIX] Remove `page` and `per_page` from parameters sent to elasticsearch
9
+ * [FIX] Remove the `wrapper` options from URL params sent to elasticsearch
10
+ * [FIX] Use `options.delete(:sort)` in model search to halt bubbling of `sort` into URL parameters [#334]
11
+ * Added prettified JSON output for logging requests and responses at the `debug` level
12
+ * Improved the Rake import task
13
+ * Allow passing of arbitrary objects in the `:as` mapping option [#446]
14
+ * Allow to define default values for Tire::Model::Persistence `:as` lambdas
15
+ * Added `@search.results.max_score`
16
+ * Changed the URI escape/unescape compatibility patch to not require Rack
17
+ * Allow using the full DSL in filter facets
18
+ * Allow complex hash options for the `term` query
19
+ * Allow passing Hash-like objects to `terms` query as well
20
+ * Implemented `respond_to?` for `Item`
21
+ * Improved support for Kaminari pagination
22
+ * Added support for the `parent` URL parameter in `Index#store`
23
+ * Added the `min_score` and `track_scores` DSL methods
24
+ * Added support for loading partial fields
25
+ * Added support for boosting query
26
+ * Added the `facet_filter` DSL method
27
+ * Allow passing `routing`, `fields` and `preference` URL parameter to Index#retrieve
28
+ * Allow building the search request step-by-step in Tire's DSL [#496]
29
+ * Added a `match` query type
30
+ * Added support for multisearch (_msearch) and the `Tire.multi_search` DSL method
31
+ * Added support for multi-search in the ActiveModel integration and in Tire::Model::Persistence
14
32
  END
15
33
  end
@@ -38,36 +38,36 @@ module Tire
38
38
  context "ActiveRecord integration" do
39
39
 
40
40
  setup do
41
- ActiveRecordArticle.destroy_all
41
+ ActiveRecordArticle.delete_all
42
42
  Tire.index('active_record_articles').delete
43
43
 
44
44
  load File.expand_path('../../models/active_record_models.rb', __FILE__)
45
45
  end
46
46
 
47
47
  teardown do
48
- ActiveRecordArticle.destroy_all
48
+ ActiveRecordArticle.delete_all
49
49
  Tire.index('active_record_articles').delete
50
50
  end
51
51
 
52
52
  should "configure mapping" do
53
53
  assert_equal 'snowball', ActiveRecordArticle.mapping[:title][:analyzer]
54
54
  assert_equal 10, ActiveRecordArticle.mapping[:title][:boost]
55
-
55
+
56
56
  assert_equal 'snowball', ActiveRecordArticle.index.mapping['active_record_article']['properties']['title']['analyzer']
57
57
  end
58
-
58
+
59
59
  should "save document into index on save and find it" do
60
60
  a = ActiveRecordArticle.new :title => 'Test'
61
61
  a.save!
62
62
  id = a.id
63
-
63
+
64
64
  a.index.refresh
65
-
65
+
66
66
  results = ActiveRecordArticle.search 'test'
67
-
67
+
68
68
  assert results.any?
69
69
  assert_equal 1, results.count
70
-
70
+
71
71
  assert_instance_of Results::Item, results.first
72
72
  assert_not_nil results.first.id
73
73
  assert_equal id.to_s, results.first.id.to_s
@@ -75,43 +75,67 @@ module Tire
75
75
  assert_not_nil results.first._score
76
76
  assert_equal 'Test', results.first.title
77
77
  end
78
-
78
+
79
+ should "remove document from index on destroy" do
80
+ a = ActiveRecordArticle.new :title => 'Test remove...'
81
+ a.save!
82
+ assert_equal 1, ActiveRecordArticle.count
83
+
84
+ a.destroy
85
+ assert_equal 0, ActiveRecordArticle.all.size
86
+
87
+ a.index.refresh
88
+ results = ActiveRecordArticle.search 'test'
89
+ assert_equal 0, results.count
90
+ end
91
+
92
+ should "return documents with scores" do
93
+ ActiveRecordArticle.create! :title => 'foo'
94
+ ActiveRecordArticle.create! :title => 'bar'
95
+
96
+ ActiveRecordArticle.index.refresh
97
+ results = ActiveRecordArticle.search 'foo OR bar^100'
98
+ assert_equal 2, results.count
99
+
100
+ assert_equal 'bar', results.first.title
101
+ end
102
+
79
103
  should "raise exception on invalid query" do
80
104
  ActiveRecordArticle.create! :title => 'Test'
81
-
105
+
82
106
  assert_raise Search::SearchRequestFailed do
83
107
  ActiveRecordArticle.search '[x'
84
108
  end
85
109
  end
86
-
110
+
87
111
  context "with eager loading" do
88
112
  setup do
89
113
  ActiveRecordArticle.destroy_all
90
114
  5.times { |n| ActiveRecordArticle.create! :title => "Test #{n+1}" }
91
115
  ActiveRecordArticle.index.refresh
92
116
  end
93
-
117
+
94
118
  should "load records on query search" do
95
119
  results = ActiveRecordArticle.search '"Test 1"', :load => true
96
-
120
+
97
121
  assert results.any?
98
122
  assert_equal ActiveRecordArticle.find(1), results.first
99
123
  end
100
-
124
+
101
125
  should "load records on block search" do
102
126
  results = ActiveRecordArticle.search :load => true do
103
127
  query { string '"Test 1"' }
104
128
  end
105
-
129
+
106
130
  assert_equal ActiveRecordArticle.find(1), results.first
107
131
  end
108
-
132
+
109
133
  should "load records with options on query search" do
110
134
  assert_equal ActiveRecordArticle.find(['1'], :include => 'comments').first,
111
135
  ActiveRecordArticle.search('"Test 1"',
112
136
  :load => { :include => 'comments' }).results.first
113
137
  end
114
-
138
+
115
139
  should "return empty collection for nonmatching query" do
116
140
  assert_nothing_raised do
117
141
  results = ActiveRecordArticle.search :load => true do
@@ -122,237 +146,284 @@ module Tire
122
146
  end
123
147
  end
124
148
  end
125
-
126
- should "remove document from index on destroy" do
127
- a = ActiveRecordArticle.new :title => 'Test remove...'
128
- a.save!
129
- assert_equal 1, ActiveRecordArticle.count
130
-
131
- a.destroy
132
- assert_equal 0, ActiveRecordArticle.all.size
133
-
134
- a.index.refresh
135
- results = ActiveRecordArticle.search 'test'
136
- assert_equal 0, results.count
137
- end
138
-
139
- should "return documents with scores" do
140
- ActiveRecordArticle.create! :title => 'foo'
141
- ActiveRecordArticle.create! :title => 'bar'
142
-
143
- ActiveRecordArticle.index.refresh
144
- results = ActiveRecordArticle.search 'foo OR bar^100'
145
- assert_equal 2, results.count
146
-
147
- assert_equal 'bar', results.first.title
148
- end
149
-
149
+
150
150
  context "with pagination" do
151
151
  setup do
152
152
  1.upto(9) { |number| ActiveRecordArticle.create :title => "Test#{number}" }
153
153
  ActiveRecordArticle.index.refresh
154
154
  end
155
-
155
+
156
156
  context "and parameter searches" do
157
-
157
+
158
158
  should "find first page with five results" do
159
159
  results = ActiveRecordArticle.search 'test*', :sort => 'title', :per_page => 5, :page => 1
160
160
  assert_equal 5, results.size
161
-
161
+
162
162
  # WillPaginate
163
163
  #
164
164
  assert_equal 2, results.total_pages
165
165
  assert_equal 1, results.current_page
166
166
  assert_equal nil, results.previous_page
167
167
  assert_equal 2, results.next_page
168
-
168
+
169
169
  # Kaminari
170
170
  #
171
171
  assert_equal 5, results.limit_value
172
172
  assert_equal 9, results.total_count
173
173
  assert_equal 2, results.num_pages
174
174
  assert_equal 0, results.offset_value
175
-
175
+
176
176
  assert_equal 'Test1', results.first.title
177
177
  end
178
-
179
- should "find next page with five results" do
178
+
179
+ should "find second page with four results" do
180
180
  results = ActiveRecordArticle.search 'test*', :sort => 'title', :per_page => 5, :page => 2
181
181
  assert_equal 4, results.size
182
-
182
+
183
183
  assert_equal 2, results.total_pages
184
184
  assert_equal 2, results.current_page
185
185
  assert_equal 1, results.previous_page
186
186
  assert_equal nil, results.next_page
187
-
187
+
188
188
  #kaminari
189
189
  assert_equal 5, results.limit_value
190
190
  assert_equal 9, results.total_count
191
191
  assert_equal 2, results.num_pages
192
192
  assert_equal 5, results.offset_value
193
-
193
+
194
194
  assert_equal 'Test6', results.first.title
195
195
  end
196
-
197
- should "find not find missing page" do
196
+
197
+ should "find not find missing (third) page" do
198
198
  results = ActiveRecordArticle.search 'test*', :sort => 'title', :per_page => 5, :page => 3
199
199
  assert_equal 0, results.size
200
-
200
+
201
201
  assert_equal 2, results.total_pages
202
202
  assert_equal 3, results.current_page
203
203
  assert_equal 2, results.previous_page
204
204
  assert_equal nil, results.next_page
205
-
205
+
206
206
  #kaminari
207
207
  assert_equal 5, results.limit_value
208
208
  assert_equal 9, results.total_count
209
209
  assert_equal 2, results.num_pages
210
210
  assert_equal 10, results.offset_value
211
-
211
+
212
212
  assert_nil results.first
213
213
  end
214
-
214
+
215
+ context "without an explicit per_page" do
216
+
217
+ should "not find a missing (second) page" do
218
+ results = ActiveRecordArticle.search 'test*', :sort => 'title', :page => 2
219
+ assert_equal 0, results.size
220
+
221
+ # WillPaginate
222
+ #
223
+ assert_equal 1, results.total_pages
224
+ assert_equal 2, results.current_page
225
+ assert_equal 1, results.previous_page
226
+ assert_equal nil, results.next_page
227
+
228
+ assert_nil results.first
229
+ end
230
+
231
+ end
232
+
215
233
  end
216
-
234
+
217
235
  context "and block searches" do
218
236
  setup { @q = 'test*' }
219
-
220
- should "find first page with five results" do
221
- results = ActiveRecordArticle.search do |search|
222
- search.query { |query| query.string @q }
223
- search.sort { by :title }
224
- search.from 0
225
- search.size 5
237
+
238
+ context "with page/per_page" do
239
+
240
+ should "find first page with five results" do
241
+ results = ActiveRecordArticle.search :page => 1, :per_page => 5 do |search|
242
+ search.query { |query| query.string @q }
243
+ search.sort { by :title }
244
+ end
245
+ assert_equal 5, results.size
246
+
247
+ assert_equal 2, results.total_pages
248
+ assert_equal 1, results.current_page
249
+ assert_equal nil, results.previous_page
250
+ assert_equal 2, results.next_page
251
+
252
+ assert_equal 'Test1', results.first.title
226
253
  end
227
- assert_equal 5, results.size
228
-
229
- assert_equal 2, results.total_pages
230
- assert_equal 1, results.current_page
231
- assert_equal nil, results.previous_page
232
- assert_equal 2, results.next_page
233
-
234
- assert_equal 'Test1', results.first.title
235
- end
236
-
237
- should "find next page with five results" do
238
- results = ActiveRecordArticle.search do |search|
239
- search.query { |query| query.string @q }
240
- search.sort { by :title }
241
- search.from 5
242
- search.size 5
254
+
255
+ should "find second page with four results" do
256
+ results = ActiveRecordArticle.search :page => 2, :per_page => 5 do |search|
257
+ search.query { |query| query.string @q }
258
+ search.sort { by :title }
259
+ end
260
+ assert_equal 4, results.size
261
+
262
+ assert_equal 2, results.total_pages
263
+ assert_equal 2, results.current_page
264
+ assert_equal 1, results.previous_page
265
+ assert_equal nil, results.next_page
266
+
267
+ assert_equal 'Test6', results.first.title
243
268
  end
244
- assert_equal 4, results.size
245
-
246
- assert_equal 2, results.total_pages
247
- assert_equal 2, results.current_page
248
- assert_equal 1, results.previous_page
249
- assert_equal nil, results.next_page
250
-
251
- assert_equal 'Test6', results.first.title
269
+
270
+ should "not find a missing (third) page" do
271
+ results = ActiveRecordArticle.search :page => 3, :per_page => 5 do |search|
272
+ search.query { |query| query.string @q }
273
+ search.sort { by :title }
274
+ end
275
+ assert_equal 0, results.size
276
+
277
+ assert_equal 2, results.total_pages
278
+ assert_equal 3, results.current_page
279
+ assert_equal 2, results.previous_page
280
+ assert_equal nil, results.next_page
281
+
282
+ assert_nil results.first
283
+ end
284
+
252
285
  end
253
-
254
- should "not find a missing page" do
255
- results = ActiveRecordArticle.search do |search|
256
- search.query { |query| query.string @q }
257
- search.sort { by :title }
258
- search.from 10
259
- search.size 5
286
+
287
+ context "with from/size" do
288
+
289
+ should "find first page with five results" do
290
+ results = ActiveRecordArticle.search do |search|
291
+ search.query { |query| query.string @q }
292
+ search.sort { by :title }
293
+ search.from 0
294
+ search.size 5
295
+ end
296
+ assert_equal 5, results.size
297
+
298
+ assert_equal 2, results.total_pages
299
+ assert_equal 1, results.current_page
300
+ assert_equal nil, results.previous_page
301
+ assert_equal 2, results.next_page
302
+
303
+ assert_equal 'Test1', results.first.title
260
304
  end
261
- assert_equal 0, results.size
262
-
263
- assert_equal 2, results.total_pages
264
- assert_equal 3, results.current_page
265
- assert_equal 2, results.previous_page
266
- assert_equal nil, results.next_page
267
-
268
- assert_nil results.first
305
+
306
+ should "find second page with five results" do
307
+ results = ActiveRecordArticle.search do |search|
308
+ search.query { |query| query.string @q }
309
+ search.sort { by :title }
310
+ search.from 5
311
+ search.size 5
312
+ end
313
+ assert_equal 4, results.size
314
+
315
+ assert_equal 2, results.total_pages
316
+ assert_equal 2, results.current_page
317
+ assert_equal 1, results.previous_page
318
+ assert_equal nil, results.next_page
319
+
320
+ assert_equal 'Test6', results.first.title
321
+ end
322
+
323
+ should "not find a missing (third) page" do
324
+ results = ActiveRecordArticle.search do |search|
325
+ search.query { |query| query.string @q }
326
+ search.sort { by :title }
327
+ search.from 10
328
+ search.size 5
329
+ end
330
+ assert_equal 0, results.size
331
+
332
+ assert_equal 2, results.total_pages
333
+ assert_equal 3, results.current_page
334
+ assert_equal 2, results.previous_page
335
+ assert_equal nil, results.next_page
336
+
337
+ assert_nil results.first
338
+ end
339
+
269
340
  end
270
-
271
- end
272
-
341
+
342
+ end
343
+
273
344
  end
274
-
345
+
275
346
  context "with proxy" do
276
-
347
+
277
348
  should "allow access to Tire instance methods" do
278
349
  a = ActiveRecordClassWithTireMethods.create :title => 'One'
279
350
  assert_equal "THIS IS MY INDEX!", a.index
280
351
  assert_instance_of Tire::Index, a.tire.index
281
352
  assert a.tire.index.exists?, "Index should exist"
282
353
  end
283
-
354
+
284
355
  should "allow access to Tire class methods" do
285
356
  class ::ActiveRecordClassWithTireMethods < ActiveRecord::Base
286
357
  def self.search(*)
287
358
  "THIS IS MY SEARCH!"
288
359
  end
289
360
  end
290
-
361
+
291
362
  ActiveRecordClassWithTireMethods.create :title => 'One'
292
363
  ActiveRecordClassWithTireMethods.tire.index.refresh
293
-
364
+
294
365
  assert_equal "THIS IS MY SEARCH!", ActiveRecordClassWithTireMethods.search
295
-
366
+
296
367
  results = ActiveRecordClassWithTireMethods.tire.search 'one'
297
-
368
+
298
369
  assert_equal 'One', results.first.title
299
370
  end
300
-
371
+
301
372
  end
302
-
373
+
303
374
  context "with dynamic index name" do
304
375
  setup do
305
376
  @a = ActiveRecordClassWithDynamicIndexName.create! :title => 'Test'
306
377
  @a.index.refresh
307
378
  end
308
-
379
+
309
380
  should "search in proper index" do
310
381
  assert_equal 'dynamic_index', ActiveRecordClassWithDynamicIndexName.index.name
311
382
  assert_equal 'dynamic_index', @a.index.name
312
-
383
+
313
384
  results = ActiveRecordClassWithDynamicIndexName.search 'test'
314
385
  assert_equal 'dynamic_index', results.first._index
315
386
  end
316
387
  end
317
-
388
+
318
389
  context "within Rails" do
319
-
390
+
320
391
  setup do
321
392
  module ::Rails; end
322
-
393
+
323
394
  a = ActiveRecordArticle.new :title => 'Test'
324
395
  a.comments.build :author => 'fool', :body => 'Works!'
325
396
  a.stats.build :pageviews => 12, :period => '2011-08'
326
397
  a.save!
327
398
  @id = a.id.to_s
328
-
399
+
329
400
  a.index.refresh
330
401
  @item = ActiveRecordArticle.search('test').first
331
402
  end
332
-
403
+
333
404
  should "have access to indexed properties" do
334
405
  assert_equal 'Test', @item.title
335
406
  assert_equal 'fool', @item.comments.first.author
336
407
  assert_equal 12, @item.stats.first.pageviews
337
408
  end
338
-
409
+
339
410
  should "load the underlying models" do
340
411
  assert_instance_of Results::Item, @item
341
412
  assert_instance_of ActiveRecordArticle, @item.load
342
413
  assert_equal 'Test', @item.load.title
343
-
414
+
344
415
  assert_instance_of Results::Item, @item.comments.first
345
416
  assert_instance_of ActiveRecordComment, @item.comments.first.load
346
417
  assert_equal 'fool', @item.comments.first.load.author
347
418
  end
348
-
419
+
349
420
  should "load the underlying model with options" do
350
421
  ActiveRecordArticle.expects(:find).with(@id, :include => 'comments')
351
422
  @item.load(:include => 'comments')
352
423
  end
353
-
424
+
354
425
  end
355
-
426
+
356
427
  context "with multiple class instances in one index" do
357
428
  setup do
358
429
  ActiveRecord::Schema.define do
@@ -439,6 +510,54 @@ module Tire
439
510
  end
440
511
 
441
512
  end
513
+
514
+ context "multi search" do
515
+ setup do
516
+ # Tire.configure { logger STDERR }
517
+ ActiveRecordArticle.create! :title => 'Test'
518
+ ActiveRecordArticle.create! :title => 'Pest'
519
+ ActiveRecordArticle.index.refresh
520
+ end
521
+
522
+ should "return multiple result sets" do
523
+ results = ActiveRecordArticle.multi_search do
524
+ search do
525
+ query { match :title, 'test' }
526
+ end
527
+ search search_type: 'count' do
528
+ query { match :title, 'pest' }
529
+ end
530
+ search :articles, index: 'articles-test', type: 'article' do
531
+ query { all }
532
+ end
533
+ end
534
+
535
+ assert_equal 3, results.size
536
+
537
+ assert_equal 1, results[0].size
538
+ assert_equal 1, results[0].total
539
+
540
+ assert_equal 0, results[1].size
541
+ assert_equal 1, results[1].total
542
+
543
+ assert_equal 5, results[:articles].size
544
+ end
545
+
546
+ should "return model instances with the :load option" do
547
+ results = ActiveRecordArticle.multi_search do
548
+ search :items do
549
+ query { match :title, 'test' }
550
+ end
551
+ search :models, :load => true do
552
+ query { match :title, 'test' }
553
+ end
554
+ end
555
+
556
+ assert_instance_of Tire::Results::Item, results[:items].first
557
+ assert_instance_of ActiveRecordArticle, results[:models].first
558
+ end
559
+
560
+ end
442
561
  end
443
562
 
444
563
  end