tire 0.1.8 → 0.1.9
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.
- 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
|