tire 0.5.4 → 0.5.5

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 (42) hide show
  1. data/.travis.yml +3 -0
  2. data/README.markdown +49 -29
  3. data/examples/rails-application-template.rb +15 -15
  4. data/examples/tire-dsl.rb +24 -24
  5. data/lib/tire.rb +2 -1
  6. data/lib/tire/alias.rb +3 -3
  7. data/lib/tire/dsl.rb +17 -19
  8. data/lib/tire/index.rb +67 -10
  9. data/lib/tire/model/callbacks.rb +1 -1
  10. data/lib/tire/model/indexing.rb +2 -2
  11. data/lib/tire/model/naming.rb +1 -1
  12. data/lib/tire/model/percolate.rb +3 -3
  13. data/lib/tire/model/persistence.rb +1 -1
  14. data/lib/tire/model/persistence/attributes.rb +2 -2
  15. data/lib/tire/model/persistence/storage.rb +2 -2
  16. data/lib/tire/model/search.rb +8 -8
  17. data/lib/tire/multi_search.rb +4 -4
  18. data/lib/tire/rubyext/ruby_1_8.rb +1 -1
  19. data/lib/tire/search.rb +1 -1
  20. data/lib/tire/search/scan.rb +1 -1
  21. data/lib/tire/tasks.rb +1 -1
  22. data/lib/tire/version.rb +7 -11
  23. data/test/integration/active_record_searchable_test.rb +42 -0
  24. data/test/integration/dis_max_queries_test.rb +1 -1
  25. data/test/integration/dsl_search_test.rb +9 -0
  26. data/test/integration/index_mapping_test.rb +82 -7
  27. data/test/integration/persistent_model_test.rb +17 -0
  28. data/test/integration/sort_test.rb +16 -0
  29. data/test/models/active_record_models.rb +9 -0
  30. data/test/models/persistent_article.rb +1 -1
  31. data/test/models/persistent_article_in_index.rb +1 -1
  32. data/test/models/persistent_article_in_namespace.rb +1 -1
  33. data/test/models/persistent_article_with_percolation.rb +5 -0
  34. data/test/models/persistent_articles_with_custom_index_name.rb +1 -1
  35. data/test/test_helper.rb +1 -1
  36. data/test/unit/index_alias_test.rb +1 -1
  37. data/test/unit/index_test.rb +79 -1
  38. data/test/unit/model_persistence_test.rb +11 -1
  39. data/test/unit/model_search_test.rb +2 -2
  40. data/test/unit/tire_test.rb +11 -7
  41. data/tire.gemspec +4 -3
  42. metadata +61 -51
@@ -32,6 +32,10 @@ module Tire
32
32
  create_table :active_record_class_with_dynamic_index_names do |t|
33
33
  t.string :title
34
34
  end
35
+ create_table :active_record_model_with_percolations do |t|
36
+ t.string :title
37
+ t.datetime :created_at, :default => 'NOW()'
38
+ end
35
39
  end
36
40
  end
37
41
 
@@ -158,6 +162,16 @@ module Tire
158
162
  assert hit['_score'] > 0
159
163
  end
160
164
  end
165
+
166
+ should "provide access to highlighted fields in hit" do
167
+ results = ActiveRecordArticle.search :load => true do
168
+ query { string '"Test 1" OR "Test 2"' }
169
+ highlight :title
170
+ end
171
+ results.each_with_hit do |result, hit|
172
+ assert_equal 1, hit['highlight']['title'].size
173
+ end
174
+ end
161
175
  end
162
176
 
163
177
  context "with pagination" do
@@ -295,6 +309,16 @@ module Tire
295
309
  assert_nil results.first
296
310
  end
297
311
 
312
+ should "find second page with four loaded models" do
313
+ results = ActiveRecordArticle.search :load => true, :page => 2, :per_page => 5 do |search|
314
+ search.query { |query| query.string @q }
315
+ search.sort { by :title }
316
+ end
317
+ assert_equal 4, results.size
318
+ assert results.all? { |r| assert_instance_of ActiveRecordArticle, r }
319
+ assert_equal 'Test6', results.first.title
320
+ end
321
+
298
322
  end
299
323
 
300
324
  context "with from/size" do
@@ -571,6 +595,24 @@ module Tire
571
595
  end
572
596
 
573
597
  end
598
+
599
+ context "percolated search" do
600
+ setup do
601
+ ActiveRecordModelWithPercolation.index.register_percolator_query('alert') { string 'warning' }
602
+ Tire.index('_percolator').refresh
603
+ end
604
+
605
+ should "return matching queries when percolating" do
606
+ a = ActiveRecordModelWithPercolation.new :title => 'Warning!'
607
+ assert_contains a.percolate, 'alert'
608
+ end
609
+
610
+ should "return matching queries when saving" do
611
+ a = ActiveRecordModelWithPercolation.create! :title => 'Warning!'
612
+ assert_contains a.matches, 'alert'
613
+ end
614
+ end
615
+
574
616
  end
575
617
 
576
618
  end
@@ -22,7 +22,7 @@ module Tire
22
22
  Tire.index('dis_max_test').delete
23
23
  end
24
24
 
25
- should "boost matches in both fields" do
25
+ should_eventually "boost matches in both fields" do
26
26
  dis_max = Tire.search 'dis_max_test' do
27
27
  query do
28
28
  dis_max do
@@ -13,6 +13,15 @@ module Tire
13
13
 
14
14
  assert_equal 2, s.results.count
15
15
  assert_equal 2, s.results.facets['tags']['count']
16
+ assert_match /_search\?pretty' -d '{/, s.to_curl, 'Make sure to ignore payload in URL params'
17
+ end
18
+
19
+ should "allow passing URL parameters" do
20
+ s = Tire.search 'articles-test', search_type: 'count', query: { match: { tags: 'ruby' } }
21
+
22
+ assert_equal 0, s.results.count
23
+ assert_equal 2, s.results.total
24
+ assert_match /_search.*search_type=count.*' -d '{/, s.to_curl
16
25
  end
17
26
 
18
27
  should "allow building search query iteratively" do
@@ -8,7 +8,7 @@ module Tire
8
8
  context "Default mapping" do
9
9
  teardown { Tire.index('mapped-index').delete; sleep 0.1 }
10
10
 
11
- should "create and return the default mapping" do
11
+ should "create and return the default mapping as a Hash" do
12
12
 
13
13
  index = Tire.index 'mapped-index' do
14
14
  create
@@ -24,17 +24,92 @@ module Tire
24
24
 
25
25
  context "Creating index with mapping" do
26
26
  teardown { Tire.index('mapped-index').delete; sleep 0.1 }
27
-
27
+
28
28
  should "create the specified mapping" do
29
-
30
29
  index = Tire.index 'mapped-index' do
31
- create :mappings => { :article => { :properties => { :title => { :type => 'string', :boost => 2.0, :store => 'yes' } } } }
30
+ create mappings: {
31
+ article: {
32
+ _all: { enabled: false },
33
+ properties: {
34
+ title: { type: 'string', boost: 2.0, store: 'yes' }
35
+ }
36
+ }
37
+ }
32
38
  end
33
39
 
34
- # p index.mapping
40
+ # p index.mapping
41
+ assert_equal false, index.mapping['article']['_all']['enabled'], index.mapping.inspect
35
42
  assert_equal 2.0, index.mapping['article']['properties']['title']['boost'], index.mapping.inspect
36
- assert_equal 'yes', index.mapping['article']['properties']['title']['store'], index.mapping.inspect
37
-
43
+ end
44
+ end
45
+
46
+ context "Update mapping" do
47
+ setup { Tire.index("mapped-index").create; sleep 1 }
48
+ teardown { Tire.index("mapped-index").delete; sleep 0.1 }
49
+
50
+ should "update the mapping for type" do
51
+ index = Tire.index("mapped-index")
52
+
53
+ index.mapping "article", :properties => { :body => { :type => "string" } }
54
+ assert_equal({ "type" => "string" }, index.mapping["article"]["properties"]["body"])
55
+
56
+ assert index.mapping("article", :properties => { :title => { :type => "string" } })
57
+
58
+ mapping = index.mapping
59
+
60
+ # Verify return value
61
+ assert mapping, index.response.inspect
62
+
63
+ # Verify response
64
+ assert_equal( { "type" => "string" }, mapping["article"]["properties"]["body"] )
65
+ assert_equal( { "type" => "string" }, mapping["article"]["properties"]["title"] )
66
+ end
67
+
68
+ should "fail to update the mapping in an incompatible way" do
69
+ index = Tire.index("mapped-index")
70
+
71
+ # 1. Update initial index mapping
72
+ assert index.mapping "article", properties: { body: { type: "string" } }
73
+ assert_equal( { "type" => "string" }, index.mapping["article"]["properties"]["body"] )
74
+
75
+ # 2. Attempt to update the mapping in incompatible way (change property type)
76
+ mapping = index.mapping "article", :properties => { :body => { :type => "integer" } }
77
+
78
+ # Verify return value
79
+ assert !mapping, index.response.inspect
80
+ #
81
+ # Verify response
82
+ assert_match /MergeMappingException/, index.response.body
83
+ end
84
+
85
+ should "honor the `ignore_conflicts` option" do
86
+ index = Tire.index("mapped-index")
87
+
88
+ # 1. Update initial index mapping
89
+ assert index.mapping "article", properties: { body: { type: "string" } }
90
+ assert_equal( { "type" => "string" }, index.mapping["article"]["properties"]["body"] )
91
+
92
+ # 2. Attempt to update the mapping in incompatible way and ignore conflicts
93
+ mapping = index.mapping "article", ignore_conflicts: true, properties: { body: { type: "integer" } }
94
+
95
+ # Verify return value (true since we ignore conflicts)
96
+ assert mapping, index.response.inspect
97
+ end
98
+
99
+ end
100
+
101
+ context "Delete mapping" do
102
+ setup { Tire.index("mapped-index").create; sleep 1 }
103
+ teardown { Tire.index("mapped-index").delete; sleep 0.1 }
104
+
105
+ should "delete the mapping for type" do
106
+ index = Tire.index("mapped-index")
107
+
108
+ # 1. Update initial index mapping
109
+ assert index.mapping 'article', properties: { body: { type: "string" } }
110
+
111
+ assert index.delete_mapping 'article'
112
+ assert index.mapping.empty?, index.response.inspect
38
113
  end
39
114
  end
40
115
 
@@ -182,6 +182,23 @@ module Tire
182
182
  end
183
183
  end
184
184
 
185
+ context "percolated search" do
186
+ setup do
187
+ PersistentArticleWithPercolation.index.register_percolator_query('alert') { string 'warning' }
188
+ Tire.index('_percolator').refresh
189
+ end
190
+
191
+ should "return matching queries when percolating" do
192
+ a = PersistentArticleWithPercolation.new :title => 'Warning!'
193
+ assert_contains a.percolate, 'alert'
194
+ end
195
+
196
+ should "return matching queries when saving" do
197
+ a = PersistentArticleWithPercolation.create :title => 'Warning!'
198
+ assert_contains a.matches, 'alert'
199
+ end
200
+ end
201
+
185
202
  end
186
203
 
187
204
  end
@@ -29,6 +29,22 @@ module Tire
29
29
  assert_equal 'Two', s.results.first[:title]
30
30
  end
31
31
 
32
+ should "sort by multiple fields" do
33
+ q = '*'
34
+ s = Tire.search('articles-test') do
35
+ query { string q }
36
+ sort do
37
+ by :words, :desc
38
+ by :title, :asc
39
+ end
40
+ end
41
+
42
+ # p s.results.to_a.map { |i| [i.title, i.sort] }
43
+ assert_equal 'Three', s.results[0][:title]
44
+ assert_equal 'Four', s.results[1][:title]
45
+ assert_equal 'Two', s.results[2][:title]
46
+ end
47
+
32
48
  end
33
49
 
34
50
  end
@@ -120,3 +120,12 @@ class ActiveRecordNamespace::MyModel < ActiveRecord::Base
120
120
  include Tire::Model::Search
121
121
  include Tire::Model::Callbacks
122
122
  end
123
+
124
+ # Model with percolation
125
+
126
+ class ActiveRecordModelWithPercolation < ActiveRecord::Base
127
+ include Tire::Model::Search
128
+ include Tire::Model::Callbacks
129
+
130
+ percolate!
131
+ end
@@ -1,4 +1,4 @@
1
- # Example class with ElasticSearch persistence
1
+ # Example class with Elasticsearch persistence
2
2
 
3
3
  class PersistentArticle
4
4
 
@@ -1,4 +1,4 @@
1
- # Example class with ElasticSearch persistence in index `persistent_articles`
1
+ # Example class with Elasticsearch persistence in index `persistent_articles`
2
2
  #
3
3
  # The `index` is `persistent_articles`
4
4
  #
@@ -1,4 +1,4 @@
1
- # Example namespaced class with ElasticSearch persistence
1
+ # Example namespaced class with Elasticsearch persistence
2
2
  #
3
3
  # The `document_type` is `my_namespace/persistent_article_in_namespace`
4
4
  #
@@ -0,0 +1,5 @@
1
+ class PersistentArticleWithPercolation
2
+ include Tire::Model::Persistence
3
+ property :title
4
+ percolate!
5
+ end
@@ -1,4 +1,4 @@
1
- # Example class with ElasticSearch persistence and custom index name
1
+ # Example class with Elasticsearch persistence and custom index name
2
2
 
3
3
  class PersistentArticleWithCustomIndexName
4
4
 
@@ -84,7 +84,7 @@ module Test::Integration
84
84
  begin
85
85
  ::RestClient.get URL
86
86
  rescue Errno::ECONNREFUSED
87
- abort "\n\n#{'-'*87}\n[ABORTED] You have to run ElasticSearch on #{URL} for integration tests\n#{'-'*87}\n\n"
87
+ abort "\n\n#{'-'*87}\n[ABORTED] You have to run Elasticsearch on #{URL} for integration tests\n#{'-'*87}\n\n"
88
88
  end
89
89
 
90
90
  ::RestClient.delete "#{URL}/articles-test" rescue nil
@@ -130,7 +130,7 @@ module Tire
130
130
 
131
131
  context "saving" do
132
132
 
133
- should "send data to ElasticSearch" do
133
+ should "send data to Elasticsearch" do
134
134
  Configuration.client.expects(:post).with do |url, json|
135
135
  url == "#{Configuration.url}/_aliases" &&
136
136
  json =~ /"index":"index_2012_05"/ &&
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  module Tire
@@ -170,7 +172,7 @@ module Tire
170
172
  }
171
173
  end
172
174
 
173
- should "return the mapping" do
175
+ should "return the mapping as a Hash" do
174
176
  json =<<-JSON
175
177
  {
176
178
  "dummy" : {
@@ -189,6 +191,33 @@ module Tire
189
191
  assert_equal 2.0, @index.mapping['article']['properties']['title']['boost']
190
192
  end
191
193
 
194
+ should "update the mapping" do
195
+ Configuration.client.expects(:put).returns(mock_response('{"ok":true,"acknowledged":true}', 200))
196
+ assert @index.mapping( 'document', { properties: { body: {type: 'string', analyzer: 'english'} } } )
197
+ end
198
+
199
+ should "fail to update the mapping when conflicts occur" do
200
+ Configuration.client.expects(:put).returns(mock_response('{"error":"MergeMappingException","status":400}', 400))
201
+ assert ! @index.mapping( 'document', { properties: { body: {type: 'string', analyzer: 'english'} } } )
202
+ end
203
+
204
+ should "raise an exception for the bang method" do
205
+ Configuration.client.expects(:put).returns(mock_response('{"error":"MergeMappingException","status":400}', 400))
206
+ assert_raise(RuntimeError) do
207
+ @index.mapping!("blah", {})
208
+ end
209
+ end
210
+
211
+ should "delete the mapping" do
212
+ Configuration.client.expects(:delete).returns(mock_response('{"ok":true}', 200))
213
+ assert @index.delete_mapping('document')
214
+ end
215
+
216
+ should "fail when deleting the mapping for non-existing type" do
217
+ Configuration.client.expects(:delete).returns(mock_response('{"error":"TypeMissingException[[dummy] type[document] missing]","status":404}', 400))
218
+ assert ! @index.delete_mapping('document')
219
+ end
220
+
192
221
  end
193
222
 
194
223
  context "settings" do
@@ -296,6 +325,16 @@ module Tire
296
325
  @index.store( {:id => 123, :title => 'Test'}, {:routing => 'abc'} )
297
326
  end
298
327
 
328
+ should "extract the replication type from options" do
329
+ Configuration.client.expects(:post).
330
+ with do |url, payload|
331
+ assert_equal "#{Configuration.url}/dummy/document/?replication=async", url
332
+ end.
333
+ returns(mock_response('{"ok":true,"_id":"test"}'))
334
+
335
+ @index.store({:title => 'Test'}, {:replication => 'async'})
336
+ end
337
+
299
338
  context "document with ID" do
300
339
 
301
340
  should "store Hash it under its ID property" do
@@ -331,6 +370,16 @@ module Tire
331
370
  assert_equal 'c', @index.get_id_from_document(document_3)
332
371
  end
333
372
 
373
+ should "escape the ID in URL" do
374
+ Configuration.client.expects(:post).with do |url, payload|
375
+ assert_equal "#{@index.url}/document/%C3%A4ccent", url
376
+ assert_equal 'äccent', MultiJson.load(payload)['id']
377
+ end.
378
+ returns(mock_response('{"ok":true,"_id":"äccent"}'))
379
+
380
+ @index.store :id => 'äccent'
381
+ end
382
+
334
383
  end
335
384
 
336
385
  end
@@ -389,6 +438,13 @@ module Tire
389
438
  end
390
439
  end
391
440
 
441
+ should "escape the ID in URL" do
442
+ Configuration.client.expects(:get).with("#{@index.url}/document/%C3%A4ccent").
443
+ returns(mock_response('{"_id":"äccent"}'))
444
+
445
+ @index.retrieve 'document', 'äccent'
446
+ end
447
+
392
448
  should "properly encode document type" do
393
449
  Configuration.client.expects(:get).with("#{@index.url}/my_namespace%2Fmy_model/id-1").
394
450
  returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
@@ -467,6 +523,13 @@ module Tire
467
523
  end
468
524
  end
469
525
 
526
+ should "escape the ID in URL" do
527
+ Configuration.client.expects(:delete).with("#{@index.url}/document/%C3%A4ccent").
528
+ returns(mock_response('{"ok":true,"_id":"äccent"}'))
529
+
530
+ @index.remove 'document', 'äccent'
531
+ end
532
+
470
533
  should "properly encode document type" do
471
534
  Configuration.client.expects(:delete).with("#{@index.url}/my_namespace%2Fmy_model/id-1").
472
535
  returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
@@ -523,6 +586,15 @@ module Tire
523
586
  assert_raise(ArgumentError) { @index.update(nil, '123', :script => 'foobar') }
524
587
  end
525
588
 
589
+ should "escape the ID in URL" do
590
+ Configuration.client.expects(:post).with do |url,payload|
591
+ assert_equal( "#{@index.url}/document/%C3%A4ccent/_update", url )
592
+ end.
593
+ returns(mock_response('{"ok":"true","_id":"äccent","_version":"2"}'))
594
+
595
+ assert @index.update('document', 'äccent', {:doc => {:foo => 'bar'}})
596
+ end
597
+
526
598
  should "raise an error when no script or partial document is passed" do
527
599
  assert_raise ArgumentError do
528
600
  @index.update('article', "42", {:foo => 'bar'})
@@ -749,6 +821,12 @@ module Tire
749
821
  @index.bulk :index, [ {:id => '1', :title => 'One'} ]
750
822
  end
751
823
 
824
+
825
+ should "return immediately with empty collection" do
826
+ Configuration.client.expects(:post).never
827
+ @index.bulk_store []
828
+ end
829
+
752
830
  end
753
831
 
754
832
  context "when importing" do