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
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