tire 0.1.8 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- data/examples/tire-dsl.rb +97 -0
- data/lib/tire/client.rb +3 -3
- data/lib/tire/index.rb +50 -3
- data/lib/tire/model/callbacks.rb +4 -0
- data/lib/tire/model/search.rb +11 -7
- data/lib/tire/search.rb +16 -12
- data/lib/tire/version.rb +1 -1
- data/test/integration/active_model_searchable_test.rb +1 -1
- data/test/integration/percolator_test.rb +86 -0
- data/test/unit/index_test.rb +117 -0
- data/test/unit/model_persistence_test.rb +4 -4
- data/test/unit/model_search_test.rb +67 -2
- data/test/unit/search_test.rb +14 -4
- metadata +6 -4
data/examples/tire-dsl.rb
CHANGED
@@ -704,6 +704,103 @@ s = Tire.search 'articles' do
|
|
704
704
|
highlight :title, :body, :options => { :tag => '<strong class="highlight">' }
|
705
705
|
end
|
706
706
|
|
707
|
+
#### Percolation
|
708
|
+
|
709
|
+
# _ElasticSearch_ comes with one very interesting, and rather unique feature:
|
710
|
+
# [_percolation_](http://www.elasticsearch.org/guide/reference/api/percolate.html).
|
711
|
+
|
712
|
+
# It works in a „reverse search“ manner to regular search workflow of adding
|
713
|
+
# documents to the index and then querying them.
|
714
|
+
# Percolation allows us to register a query, and ask if a specific document
|
715
|
+
# matches it, either on demand, or immediately as the document is being indexed.
|
716
|
+
|
717
|
+
# Let's review an example for an index named _weather_.
|
718
|
+
# We will register three queries for percolation against this index.
|
719
|
+
#
|
720
|
+
index = Tire.index('weather') do
|
721
|
+
delete
|
722
|
+
create
|
723
|
+
|
724
|
+
# First, a query named _warning_,
|
725
|
+
#
|
726
|
+
register_percolator_query('warning', :tags => ['warning']) { string 'warning OR severe OR extreme' }
|
727
|
+
|
728
|
+
# a query named _tsunami_,
|
729
|
+
#
|
730
|
+
register_percolator_query('tsunami', :tags => ['tsunami']) { string 'tsunami' }
|
731
|
+
|
732
|
+
# and a query named _floods_.
|
733
|
+
#
|
734
|
+
register_percolator_query('floods', :tags => ['floods']) { string 'flood*' }
|
735
|
+
|
736
|
+
end
|
737
|
+
|
738
|
+
# Notice, that we have added a _tags_ field to the query document, because it behaves
|
739
|
+
# just like any other document in _ElasticSearch_.
|
740
|
+
|
741
|
+
# We will refresh the `_percolator` index for immediate access.
|
742
|
+
#
|
743
|
+
Tire.index('_percolator').refresh
|
744
|
+
|
745
|
+
# Now, let's _percolate_ a document containing some trigger words against all registered queries.
|
746
|
+
#
|
747
|
+
matches = index.percolate(:message => '[Warning] Extreme flooding expected after tsunami wave.')
|
748
|
+
|
749
|
+
# The result will contain, unsurprisingly, names of all the three registered queries:
|
750
|
+
#
|
751
|
+
# Matching queries: ["floods", "tsunami", "warning"]
|
752
|
+
#
|
753
|
+
puts "Matching queries: " + matches.inspect
|
754
|
+
|
755
|
+
# We can filter the executed queries with a regular _ElasticSearch_ query passed as a block to
|
756
|
+
# the `percolate` method.
|
757
|
+
#
|
758
|
+
matches = index.percolate(:message => '[Warning] Extreme flooding expected after tsunami wave.') do
|
759
|
+
# Let's use a _terms_ query against the `tags` field.
|
760
|
+
term :tags, 'tsunami'
|
761
|
+
end
|
762
|
+
|
763
|
+
# In this case, the result will contain only the name of the “tsunami” query.
|
764
|
+
#
|
765
|
+
# Matching queries: ["tsunami"]
|
766
|
+
#
|
767
|
+
puts "Matching queries: " + matches.inspect
|
768
|
+
|
769
|
+
# What if we percolate another document, without the “tsunami” trigger word?
|
770
|
+
#
|
771
|
+
matches = index.percolate(:message => '[Warning] Extreme temperatures expected.') { term :tags, 'tsunami' }
|
772
|
+
|
773
|
+
# As expected, we will get an empty array:
|
774
|
+
#
|
775
|
+
# Matching queries: []
|
776
|
+
#
|
777
|
+
puts "Matching queries: " + matches.inspect
|
778
|
+
|
779
|
+
# Well, that's of course immensely useful for real-time search systems. But, there's more.
|
780
|
+
# We can _percolate_ a document _at the same time_ it is being stored in the index,
|
781
|
+
# getting back a list of matching queries.
|
782
|
+
|
783
|
+
# Let's store a document with some trigger words in the index, and mark it for percolation.
|
784
|
+
#
|
785
|
+
response = index.store :message => '[Warning] Severe floods expected after tsunami wave.', :percolate => true
|
786
|
+
|
787
|
+
# We will get the names of all matching queries in response.
|
788
|
+
#
|
789
|
+
# Matching queries: ["floods", "tsunami", "warning"]
|
790
|
+
#
|
791
|
+
puts "Matching queries: " + response['matches'].inspect
|
792
|
+
|
793
|
+
# As with the _percolate_ example, we can filter the executed queries.
|
794
|
+
#
|
795
|
+
response = index.store :message => '[Warning] Severe floods expected after tsunami wave.',
|
796
|
+
# Let's use a simple string query for the “tsunami” tag.
|
797
|
+
:percolate => 'tags:tsunami'
|
798
|
+
|
799
|
+
# Unsurprisingly, the response will contain just the name of the “tsunami” query.
|
800
|
+
#
|
801
|
+
# Matching queries: ["tsunami"]
|
802
|
+
#
|
803
|
+
puts "Matching queries: " + response['matches'].inspect
|
707
804
|
|
708
805
|
### ActiveModel Integration
|
709
806
|
|
data/lib/tire/client.rb
CHANGED
@@ -3,7 +3,7 @@ module Tire
|
|
3
3
|
module Client
|
4
4
|
|
5
5
|
class Base
|
6
|
-
def get(url)
|
6
|
+
def get(url, data=nil)
|
7
7
|
raise_no_method_error
|
8
8
|
end
|
9
9
|
def post(url, data)
|
@@ -21,8 +21,8 @@ module Tire
|
|
21
21
|
end
|
22
22
|
|
23
23
|
class RestClient < Base
|
24
|
-
def self.get(url)
|
25
|
-
::RestClient.get url
|
24
|
+
def self.get(url, data=nil)
|
25
|
+
::RestClient::Request.new(:method => :get, :url => url, :payload => data).execute
|
26
26
|
end
|
27
27
|
def self.post(url, data)
|
28
28
|
::RestClient.post url, data
|
data/lib/tire/index.rb
CHANGED
@@ -42,6 +42,7 @@ module Tire
|
|
42
42
|
|
43
43
|
def store(*args)
|
44
44
|
# TODO: Infer type from the document (hash property, method)
|
45
|
+
# TODO: Refactor common logic for getting id, JSON, into private methods
|
45
46
|
|
46
47
|
if args.size > 1
|
47
48
|
(type, document = args)
|
@@ -49,6 +50,11 @@ module Tire
|
|
49
50
|
(document = args.pop; type = :document)
|
50
51
|
end
|
51
52
|
|
53
|
+
if document.is_a?(Hash)
|
54
|
+
percolate = document.delete(:percolate)
|
55
|
+
percolate = "*" if percolate === true
|
56
|
+
end
|
57
|
+
|
52
58
|
old_verbose, $VERBOSE = $VERBOSE, nil # Silence Object#id deprecation warnings
|
53
59
|
id = case true
|
54
60
|
when document.is_a?(Hash) then document[:id] || document['id']
|
@@ -62,7 +68,8 @@ module Tire
|
|
62
68
|
else raise ArgumentError, "Please pass a JSON string or object with a 'to_indexed_json' method"
|
63
69
|
end
|
64
70
|
|
65
|
-
url
|
71
|
+
url = id ? "#{Configuration.url}/#{@name}/#{type}/#{id}" : "#{Configuration.url}/#{@name}/#{type}/"
|
72
|
+
url += "?percolate=#{percolate}" if percolate
|
66
73
|
|
67
74
|
@response = Configuration.client.post url, document
|
68
75
|
MultiJson.decode(@response.body)
|
@@ -75,8 +82,6 @@ module Tire
|
|
75
82
|
end
|
76
83
|
|
77
84
|
def bulk_store documents
|
78
|
-
create unless exists?
|
79
|
-
|
80
85
|
payload = documents.map do |document|
|
81
86
|
old_verbose, $VERBOSE = $VERBOSE, nil # Silence Object#id deprecation warnings
|
82
87
|
id = case
|
@@ -209,6 +214,48 @@ module Tire
|
|
209
214
|
logged(error, '_close', curl)
|
210
215
|
end
|
211
216
|
|
217
|
+
def register_percolator_query(name, options={}, &block)
|
218
|
+
options[:query] = Search::Query.new(&block).to_hash if block_given?
|
219
|
+
|
220
|
+
@response = Configuration.client.put "#{Configuration.url}/_percolator/#{@name}/#{name}", MultiJson.encode(options)
|
221
|
+
MultiJson.decode(@response.body)['ok']
|
222
|
+
rescue Exception => error
|
223
|
+
raise
|
224
|
+
ensure
|
225
|
+
curl = %Q|curl -X POST "#{Configuration.url}/_percolator/#{@name}/"|
|
226
|
+
logged(error, '_percolator', curl)
|
227
|
+
end
|
228
|
+
|
229
|
+
def percolate(*args, &block)
|
230
|
+
# TODO: Infer type from the document (hash property, method)
|
231
|
+
|
232
|
+
if args.size > 1
|
233
|
+
(type, document = args)
|
234
|
+
else
|
235
|
+
(document = args.pop; type = :document)
|
236
|
+
end
|
237
|
+
|
238
|
+
document = case true
|
239
|
+
when document.is_a?(String) then document
|
240
|
+
when document.respond_to?(:to_hash) then document.to_hash
|
241
|
+
else raise ArgumentError, "Please pass a JSON string or object with a 'to_hash' method"
|
242
|
+
end
|
243
|
+
|
244
|
+
query = Search::Query.new(&block).to_hash if block_given?
|
245
|
+
|
246
|
+
payload = { :doc => document }
|
247
|
+
payload.update( :query => query ) if query
|
248
|
+
|
249
|
+
@response = Configuration.client.get "#{Configuration.url}/#{@name}/#{type}/_percolate", payload.to_json
|
250
|
+
MultiJson.decode(@response.body)['matches']
|
251
|
+
|
252
|
+
rescue Exception => error
|
253
|
+
# raise
|
254
|
+
ensure
|
255
|
+
curl = %Q|curl -X POST "#{Configuration.url}/#{@name}/#{type}/_percolate"|
|
256
|
+
logged(error, '_percolate', curl)
|
257
|
+
end
|
258
|
+
|
212
259
|
def logged(error=nil, endpoint='/', curl='')
|
213
260
|
if Configuration.logger
|
214
261
|
|
data/lib/tire/model/callbacks.rb
CHANGED
data/lib/tire/model/search.rb
CHANGED
@@ -14,7 +14,7 @@ module Tire
|
|
14
14
|
extend ClassMethods
|
15
15
|
include InstanceMethods
|
16
16
|
|
17
|
-
['_score', '_type', '_index', '_version', 'sort', 'highlight'].each do |attr|
|
17
|
+
['_score', '_type', '_index', '_version', 'sort', 'highlight', 'matches'].each do |attr|
|
18
18
|
# TODO: Find a sane way to add attributes like _score for ActiveRecord -
|
19
19
|
# `define_attribute_methods [attr]` does not work in AR.
|
20
20
|
define_method("#{attr}=") { |value| @attributes ||= {}; @attributes[attr] = value }
|
@@ -68,6 +68,7 @@ module Tire
|
|
68
68
|
module InstanceMethods
|
69
69
|
|
70
70
|
def score
|
71
|
+
STDERR.puts "DEPRECATED! Please use #{self.class}#_score instead."
|
71
72
|
attributes['_score']
|
72
73
|
end
|
73
74
|
|
@@ -76,12 +77,15 @@ module Tire
|
|
76
77
|
end
|
77
78
|
|
78
79
|
def update_elastic_search_index
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
80
|
+
_run_update_elastic_search_index_callbacks do
|
81
|
+
if destroyed?
|
82
|
+
index.remove document_type, self
|
83
|
+
else
|
84
|
+
response = index.store document_type, self
|
85
|
+
self.id ||= response['_id'] if self.respond_to?(:id=)
|
86
|
+
self.matches = response['matches']
|
87
|
+
self
|
88
|
+
end
|
85
89
|
end
|
86
90
|
end
|
87
91
|
|
data/lib/tire/search.rb
CHANGED
@@ -65,7 +65,7 @@ module Tire
|
|
65
65
|
|
66
66
|
def perform
|
67
67
|
@url = "#{Configuration.url}/#{indices.join(',')}/_search"
|
68
|
-
@response = Configuration.client.
|
68
|
+
@response = Configuration.client.get(@url, self.to_json)
|
69
69
|
@json = MultiJson.decode(@response.body)
|
70
70
|
@results = Results::Collection.new(@json, @options)
|
71
71
|
self
|
@@ -77,20 +77,24 @@ module Tire
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def to_curl
|
80
|
-
%Q|curl -X
|
80
|
+
%Q|curl -X GET "#{Configuration.url}/#{indices.join(',')}/_search?pretty=true" -d '#{self.to_json}'|
|
81
81
|
end
|
82
82
|
|
83
|
-
def
|
83
|
+
def to_hash
|
84
84
|
request = {}
|
85
|
-
request.update( { :query => @query } )
|
86
|
-
request.update( { :sort => @sort
|
87
|
-
request.update( { :facets => @facets } ) if @facets
|
88
|
-
@filters.each { |filter| request.update( { :filter => filter } ) } if @filters
|
89
|
-
request.update( { :highlight => @highlight } ) if @highlight
|
90
|
-
request.update( { :size => @size } )
|
91
|
-
request.update( { :from => @from } )
|
92
|
-
request.update( { :fields => @fields } )
|
93
|
-
request
|
85
|
+
request.update( { :query => @query.to_hash } ) if @query
|
86
|
+
request.update( { :sort => @sort.to_ary } ) if @sort
|
87
|
+
request.update( { :facets => @facets.to_hash } ) if @facets
|
88
|
+
@filters.each { |filter| request.update( { :filter => filter.to_hash } ) } if @filters
|
89
|
+
request.update( { :highlight => @highlight.to_hash } ) if @highlight
|
90
|
+
request.update( { :size => @size } ) if @size
|
91
|
+
request.update( { :from => @from } ) if @from
|
92
|
+
request.update( { :fields => @fields } ) if @fields
|
93
|
+
request
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_json
|
97
|
+
to_hash.to_json
|
94
98
|
end
|
95
99
|
|
96
100
|
def logged(error=nil)
|
data/lib/tire/version.rb
CHANGED
@@ -44,7 +44,7 @@ module Tire
|
|
44
44
|
assert_equal 1, results.count
|
45
45
|
assert_instance_of SupermodelArticle, results.first
|
46
46
|
assert_equal 'Test', results.first.title
|
47
|
-
assert_not_nil results.first.
|
47
|
+
assert_not_nil results.first._score
|
48
48
|
assert_equal id, results.first.id
|
49
49
|
end
|
50
50
|
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
|
5
|
+
class PercolatorIntegrationTest < Test::Unit::TestCase
|
6
|
+
include Test::Integration
|
7
|
+
|
8
|
+
context "Percolator" do
|
9
|
+
setup do
|
10
|
+
@index = Tire.index('percolator-test')
|
11
|
+
@index.create
|
12
|
+
end
|
13
|
+
teardown { @index.delete }
|
14
|
+
|
15
|
+
context "when registering a query" do
|
16
|
+
should "register query as a Hash" do
|
17
|
+
query = { :query => { :query_string => { :query => 'warning' } } }
|
18
|
+
assert @index.register_percolator_query('alert', query)
|
19
|
+
sleep 0.1
|
20
|
+
|
21
|
+
percolator = Configuration.client.get("#{Configuration.url}/_percolator/percolator-test/alert")
|
22
|
+
assert percolator
|
23
|
+
end
|
24
|
+
|
25
|
+
should "register query as block" do
|
26
|
+
assert @index.register_percolator_query('alert') { string 'warning' }
|
27
|
+
sleep 0.1
|
28
|
+
|
29
|
+
percolator = Configuration.client.get("#{Configuration.url}/_percolator/percolator-test/alert")
|
30
|
+
assert percolator
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when percolating a document" do
|
36
|
+
setup do
|
37
|
+
@index.register_percolator_query('alert') { string 'warning' }
|
38
|
+
@index.register_percolator_query('gantz') { string '"y u no match"' }
|
39
|
+
@index.register_percolator_query('weather', :tags => ['weather']) { string 'severe' }
|
40
|
+
sleep 0.1
|
41
|
+
end
|
42
|
+
|
43
|
+
should "return an empty array when no query matches" do
|
44
|
+
matches = @index.percolate :message => 'Situation normal'
|
45
|
+
assert_equal [], matches
|
46
|
+
end
|
47
|
+
|
48
|
+
should "return an array of matching query names" do
|
49
|
+
matches = @index.percolate :message => 'Severe weather warning'
|
50
|
+
assert_equal ['alert','weather'], matches.sort
|
51
|
+
end
|
52
|
+
|
53
|
+
should "return an array of matching query names for specific percolated queries" do
|
54
|
+
matches = @index.percolate(:message => 'Severe weather warning') { term :tags, 'weather' }
|
55
|
+
assert_equal ['weather'], matches
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when storing document and percolating it" do
|
60
|
+
setup do
|
61
|
+
@index.register_percolator_query('alert') { string 'warning' }
|
62
|
+
@index.register_percolator_query('gantz') { string '"y u no match"' }
|
63
|
+
@index.register_percolator_query('weather', :tags => ['weather']) { string 'severe' }
|
64
|
+
sleep 0.1
|
65
|
+
end
|
66
|
+
|
67
|
+
should "return an empty array when no query matches" do
|
68
|
+
response = @index.store :message => 'Situation normal', :percolate => true
|
69
|
+
assert_equal [], response['matches']
|
70
|
+
end
|
71
|
+
|
72
|
+
should "return an array of matching query names" do
|
73
|
+
response = @index.store :message => 'Severe weather warning', :percolate => true
|
74
|
+
assert_equal ['alert','weather'], response['matches'].sort
|
75
|
+
end
|
76
|
+
|
77
|
+
should "return an array of matching query names for specific percolated queries" do
|
78
|
+
response = @index.store :message => 'Severe weather warning', :percolate => 'tags:weather'
|
79
|
+
assert_equal ['weather'], response['matches']
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
data/test/unit/index_test.rb
CHANGED
@@ -393,6 +393,123 @@ module Tire
|
|
393
393
|
|
394
394
|
end
|
395
395
|
|
396
|
+
context "when percolating" do
|
397
|
+
|
398
|
+
should "register percolator query as a Hash" do
|
399
|
+
query = { :query => { :query_string => { :query => 'foo' } } }
|
400
|
+
Configuration.client.expects(:post).with do |url, payload|
|
401
|
+
payload = MultiJson.decode(payload)
|
402
|
+
url == "#{Configuration.url}/_percolator/dummy/my-query" &&
|
403
|
+
payload['query']['query_string']['query'] == 'foo'
|
404
|
+
end.
|
405
|
+
returns(mock_response('{
|
406
|
+
"ok" : true,
|
407
|
+
"_index" : "_percolator",
|
408
|
+
"_type" : "dummy",
|
409
|
+
"_id" : "my-query",
|
410
|
+
"_version" : 1
|
411
|
+
}'))
|
412
|
+
|
413
|
+
@index.register_percolator_query 'my-query', query
|
414
|
+
end
|
415
|
+
|
416
|
+
should "register percolator query as a block" do
|
417
|
+
Configuration.client.expects(:post).with do |url, payload|
|
418
|
+
payload = MultiJson.decode(payload)
|
419
|
+
url == "#{Configuration.url}/_percolator/dummy/my-query" &&
|
420
|
+
payload['query']['query_string']['query'] == 'foo'
|
421
|
+
end.
|
422
|
+
returns(mock_response('{
|
423
|
+
"ok" : true,
|
424
|
+
"_index" : "_percolator",
|
425
|
+
"_type" : "dummy",
|
426
|
+
"_id" : "my-query",
|
427
|
+
"_version" : 1
|
428
|
+
}'))
|
429
|
+
|
430
|
+
@index.register_percolator_query 'my-query' do
|
431
|
+
string 'foo'
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
should "register percolator query with a key" do
|
436
|
+
query = { :query => { :query_string => { :query => 'foo' } },
|
437
|
+
:tags => ['alert'] }
|
438
|
+
|
439
|
+
Configuration.client.expects(:post).with do |url, payload|
|
440
|
+
payload = MultiJson.decode(payload)
|
441
|
+
url == "#{Configuration.url}/_percolator/dummy/my-query" &&
|
442
|
+
payload['query']['query_string']['query'] == 'foo'
|
443
|
+
payload['tags'] == ['alert']
|
444
|
+
end.
|
445
|
+
returns(mock_response('{
|
446
|
+
"ok" : true,
|
447
|
+
"_index" : "_percolator",
|
448
|
+
"_type" : "dummy",
|
449
|
+
"_id" : "my-query",
|
450
|
+
"_version" : 1
|
451
|
+
}'))
|
452
|
+
|
453
|
+
assert @index.register_percolator_query('my-query', query)
|
454
|
+
end
|
455
|
+
|
456
|
+
should "percolate document against all registered queries" do
|
457
|
+
Configuration.client.expects(:post).with do |url,payload|
|
458
|
+
payload = MultiJson.decode(payload)
|
459
|
+
url == "#{Configuration.url}/dummy/document/_percolate" &&
|
460
|
+
payload['doc']['title'] == 'Test'
|
461
|
+
end.
|
462
|
+
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
463
|
+
|
464
|
+
matches = @index.percolate :title => 'Test'
|
465
|
+
assert_equal ["alerts"], matches
|
466
|
+
end
|
467
|
+
|
468
|
+
should "percolate a typed document against all registered queries" do
|
469
|
+
Configuration.client.expects(:post).with do |url,payload|
|
470
|
+
payload = MultiJson.decode(payload)
|
471
|
+
url == "#{Configuration.url}/dummy/article/_percolate" &&
|
472
|
+
payload['doc']['title'] == 'Test'
|
473
|
+
end.
|
474
|
+
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
475
|
+
|
476
|
+
matches = @index.percolate :article, :title => 'Test'
|
477
|
+
assert_equal ["alerts"], matches
|
478
|
+
end
|
479
|
+
|
480
|
+
should "percolate document against specific queries" do
|
481
|
+
Configuration.client.expects(:post).with do |url,payload|
|
482
|
+
payload = MultiJson.decode(payload)
|
483
|
+
# p [url, payload]
|
484
|
+
url == "#{Configuration.url}/dummy/document/_percolate" &&
|
485
|
+
payload['doc']['title'] == 'Test' &&
|
486
|
+
payload['query']['query_string']['query'] == 'tag:alerts'
|
487
|
+
end.
|
488
|
+
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
489
|
+
|
490
|
+
matches = @index.percolate(:title => 'Test') { string 'tag:alerts' }
|
491
|
+
assert_equal ["alerts"], matches
|
492
|
+
end
|
493
|
+
|
494
|
+
context "when storing document" do
|
495
|
+
|
496
|
+
should "percolate document against all registered queries" do
|
497
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/dummy/article/?percolate=*", '{"title":"Test"}').
|
498
|
+
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
499
|
+
@index.store :article, :title => 'Test', :percolate => true
|
500
|
+
end
|
501
|
+
|
502
|
+
should "percolate document against specific queries" do
|
503
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/dummy/article/?percolate=tag:alerts", '{"title":"Test"}').
|
504
|
+
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
505
|
+
response = @index.store :article, :title => 'Test', :percolate => 'tag:alerts'
|
506
|
+
assert_equal response['matches'], ['alerts']
|
507
|
+
end
|
508
|
+
|
509
|
+
end
|
510
|
+
|
511
|
+
end
|
512
|
+
|
396
513
|
end
|
397
514
|
|
398
515
|
end
|
@@ -86,21 +86,21 @@ module Tire
|
|
86
86
|
end
|
87
87
|
|
88
88
|
should "find document by list of IDs" do
|
89
|
-
Configuration.client.expects(:
|
89
|
+
Configuration.client.expects(:get).returns(mock_response(@find_last_two.to_json))
|
90
90
|
documents = PersistentArticle.find 2, 3
|
91
91
|
|
92
92
|
assert_equal 2, documents.count
|
93
93
|
end
|
94
94
|
|
95
95
|
should "find document by array of IDs" do
|
96
|
-
Configuration.client.expects(:
|
96
|
+
Configuration.client.expects(:get).returns(mock_response(@find_last_two.to_json))
|
97
97
|
documents = PersistentArticle.find [2, 3]
|
98
98
|
|
99
99
|
assert_equal 2, documents.count
|
100
100
|
end
|
101
101
|
|
102
102
|
should "find all documents" do
|
103
|
-
Configuration.client.stubs(:
|
103
|
+
Configuration.client.stubs(:get).returns(mock_response(@find_all.to_json))
|
104
104
|
documents = PersistentArticle.all
|
105
105
|
|
106
106
|
assert_equal 3, documents.count
|
@@ -109,7 +109,7 @@ module Tire
|
|
109
109
|
end
|
110
110
|
|
111
111
|
should "find first document" do
|
112
|
-
Configuration.client.expects(:
|
112
|
+
Configuration.client.expects(:get).returns(mock_response(@find_first.to_json))
|
113
113
|
document = PersistentArticle.first
|
114
114
|
|
115
115
|
assert_equal 'First', document.attributes['title']
|
@@ -1,5 +1,16 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
+
class ModelWithIndexCallbacks
|
4
|
+
extend ActiveModel::Naming
|
5
|
+
extend ActiveModel::Callbacks
|
6
|
+
|
7
|
+
include Tire::Model::Search
|
8
|
+
include Tire::Model::Callbacks
|
9
|
+
|
10
|
+
def destroyed?; false; end
|
11
|
+
def serializable_hash; {:one => 1}; end
|
12
|
+
end
|
13
|
+
|
3
14
|
module Tire
|
4
15
|
module Model
|
5
16
|
|
@@ -20,6 +31,11 @@ module Tire
|
|
20
31
|
assert_respond_to ActiveModelArticle, :search
|
21
32
|
end
|
22
33
|
|
34
|
+
should "have the `update_elastic_search_index` callback methods defined" do
|
35
|
+
assert_respond_to ::ModelWithIndexCallbacks, :before_update_elastic_search_index
|
36
|
+
assert_respond_to ::ModelWithIndexCallbacks, :after_update_elastic_search_index
|
37
|
+
end
|
38
|
+
|
23
39
|
should_eventually "contain all Tire class/instance methods in a proxy object" do
|
24
40
|
end
|
25
41
|
|
@@ -70,7 +86,7 @@ module Tire
|
|
70
86
|
|
71
87
|
should "wrap results in proper class with ID and score and not change the original wrapper" do
|
72
88
|
response = { 'hits' => { 'hits' => [{'_id' => 1, '_score' => 0.8, '_source' => { 'title' => 'Article' }}] } }
|
73
|
-
Configuration.client.expects(:
|
89
|
+
Configuration.client.expects(:get).returns(mock_response(response.to_json))
|
74
90
|
|
75
91
|
collection = ActiveModelArticle.search 'foo'
|
76
92
|
assert_instance_of Results::Collection, collection
|
@@ -200,7 +216,7 @@ module Tire
|
|
200
216
|
|
201
217
|
should "store the record in index on :update_elastic_search_index when saved" do
|
202
218
|
@model = ActiveModelArticleWithCallbacks.new
|
203
|
-
Tire::Index.any_instance.expects(:store)
|
219
|
+
Tire::Index.any_instance.expects(:store).returns({})
|
204
220
|
|
205
221
|
@model.save
|
206
222
|
end
|
@@ -226,6 +242,7 @@ module Tire
|
|
226
242
|
|
227
243
|
class ::ModelWithCustomMapping
|
228
244
|
extend ActiveModel::Naming
|
245
|
+
extend ActiveModel::Callbacks
|
229
246
|
|
230
247
|
include Tire::Model::Search
|
231
248
|
include Tire::Model::Callbacks
|
@@ -241,6 +258,52 @@ module Tire
|
|
241
258
|
|
242
259
|
end
|
243
260
|
|
261
|
+
context "with index update callbacks" do
|
262
|
+
setup do
|
263
|
+
class ::ModelWithIndexCallbacks
|
264
|
+
_update_elastic_search_index_callbacks.clear
|
265
|
+
def notify; end
|
266
|
+
end
|
267
|
+
|
268
|
+
response = { 'ok' => true,
|
269
|
+
'_id' => 1,
|
270
|
+
'matches' => ['foo'] }
|
271
|
+
Configuration.client.expects(:post).returns(mock_response(response.to_json))
|
272
|
+
end
|
273
|
+
|
274
|
+
should "run the callback defined as block" do
|
275
|
+
class ::ModelWithIndexCallbacks
|
276
|
+
after_update_elastic_search_index { self.go! }
|
277
|
+
end
|
278
|
+
|
279
|
+
@model = ::ModelWithIndexCallbacks.new
|
280
|
+
@model.expects(:go!)
|
281
|
+
|
282
|
+
@model.update_elastic_search_index
|
283
|
+
end
|
284
|
+
|
285
|
+
should "run the callback defined as symbol" do
|
286
|
+
class ::ModelWithIndexCallbacks
|
287
|
+
after_update_elastic_search_index :notify
|
288
|
+
|
289
|
+
def notify; self.go!; end
|
290
|
+
end
|
291
|
+
|
292
|
+
@model = ::ModelWithIndexCallbacks.new
|
293
|
+
@model.expects(:go!)
|
294
|
+
|
295
|
+
@model.update_elastic_search_index
|
296
|
+
end
|
297
|
+
|
298
|
+
should "set the 'matches' property from percolated response" do
|
299
|
+
@model = ::ModelWithIndexCallbacks.new
|
300
|
+
@model.update_elastic_search_index
|
301
|
+
|
302
|
+
assert_equal ['foo'], @model.matches
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
|
244
307
|
context "serialization" do
|
245
308
|
setup { Tire::Index.any_instance.stubs(:create).returns(true) }
|
246
309
|
|
@@ -253,6 +316,7 @@ module Tire
|
|
253
316
|
|
254
317
|
class ::ModelWithoutMapping
|
255
318
|
extend ActiveModel::Naming
|
319
|
+
extend ActiveModel::Callbacks
|
256
320
|
include ActiveModel::Serialization
|
257
321
|
include Tire::Model::Search
|
258
322
|
include Tire::Model::Callbacks
|
@@ -279,6 +343,7 @@ module Tire
|
|
279
343
|
|
280
344
|
class ::ModelWithMapping
|
281
345
|
extend ActiveModel::Naming
|
346
|
+
extend ActiveModel::Callbacks
|
282
347
|
include ActiveModel::Serialization
|
283
348
|
include Tire::Model::Search
|
284
349
|
include Tire::Model::Callbacks
|
data/test/unit/search_test.rb
CHANGED
@@ -43,7 +43,7 @@ module Tire
|
|
43
43
|
s = Search::Search.new('index') do
|
44
44
|
query { string 'title:foo' }
|
45
45
|
end
|
46
|
-
assert_equal %q|curl -X
|
46
|
+
assert_equal %q|curl -X GET "http://localhost:9200/index/_search?pretty=true" -d | +
|
47
47
|
%q|'{"query":{"query_string":{"query":"title:foo"}}}'|,
|
48
48
|
s.to_curl
|
49
49
|
end
|
@@ -55,6 +55,16 @@ module Tire
|
|
55
55
|
assert_match /index_1,index_2/, s.to_curl
|
56
56
|
end
|
57
57
|
|
58
|
+
should "return itself as a Hash" do
|
59
|
+
s = Search::Search.new('index_1', 'index_2') do
|
60
|
+
query { string 'title:foo' }
|
61
|
+
end
|
62
|
+
assert_nothing_raised do
|
63
|
+
assert_instance_of Hash, s.to_hash
|
64
|
+
assert_equal "title:foo", s.to_hash[:query][:query_string][:query]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
58
68
|
should "allow chaining" do
|
59
69
|
assert_nothing_raised do
|
60
70
|
Search::Search.new('index').query { }.sort { title 'desc' }.size(5).sort { name 'asc' }.from(1)
|
@@ -63,7 +73,7 @@ module Tire
|
|
63
73
|
|
64
74
|
should "perform the search" do
|
65
75
|
response = stub(:body => '{"took":1,"hits":[]}', :code => 200)
|
66
|
-
Configuration.client.expects(:
|
76
|
+
Configuration.client.expects(:get).returns(response)
|
67
77
|
Results::Collection.expects(:new).returns([])
|
68
78
|
|
69
79
|
s = Search::Search.new('index')
|
@@ -73,7 +83,7 @@ module Tire
|
|
73
83
|
end
|
74
84
|
|
75
85
|
should "print debugging information on exception and re-raise it" do
|
76
|
-
Configuration.client.expects(:
|
86
|
+
Configuration.client.expects(:get).raises(RestClient::InternalServerError)
|
77
87
|
STDERR.expects(:puts)
|
78
88
|
|
79
89
|
s = Search::Search.new('index')
|
@@ -84,7 +94,7 @@ module Tire
|
|
84
94
|
Configuration.logger STDERR
|
85
95
|
|
86
96
|
response = stub(:body => '{"took":1,"hits":[]}', :code => 200)
|
87
|
-
Configuration.client.expects(:
|
97
|
+
Configuration.client.expects(:get).returns(response)
|
88
98
|
|
89
99
|
Results::Collection.expects(:new).returns([])
|
90
100
|
Configuration.logger.expects(:log_request).returns(true)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tire
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 9
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 9
|
10
|
+
version: 0.1.9
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Karel Minarik
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-06-
|
18
|
+
date: 2011-06-13 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -300,6 +300,7 @@ files:
|
|
300
300
|
- test/integration/highlight_test.rb
|
301
301
|
- test/integration/index_mapping_test.rb
|
302
302
|
- test/integration/index_store_test.rb
|
303
|
+
- test/integration/percolator_test.rb
|
303
304
|
- test/integration/persistent_model_test.rb
|
304
305
|
- test/integration/query_string_test.rb
|
305
306
|
- test/integration/results_test.rb
|
@@ -384,6 +385,7 @@ test_files:
|
|
384
385
|
- test/integration/highlight_test.rb
|
385
386
|
- test/integration/index_mapping_test.rb
|
386
387
|
- test/integration/index_store_test.rb
|
388
|
+
- test/integration/percolator_test.rb
|
387
389
|
- test/integration/persistent_model_test.rb
|
388
390
|
- test/integration/query_string_test.rb
|
389
391
|
- test/integration/results_test.rb
|