tire 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +18 -2
- data/README.markdown +2 -2
- data/examples/tire-dsl.rb +1 -1
- data/lib/tire.rb +1 -0
- data/lib/tire/http/clients/faraday.rb +71 -0
- data/lib/tire/index.rb +30 -7
- data/lib/tire/logger.rb +1 -1
- data/lib/tire/model/persistence.rb +2 -3
- data/lib/tire/model/persistence/finders.rb +2 -2
- data/lib/tire/model/persistence/storage.rb +3 -5
- data/lib/tire/model/search.rb +2 -0
- data/lib/tire/results/collection.rb +8 -8
- data/lib/tire/results/item.rb +4 -1
- data/lib/tire/search.rb +30 -2
- data/lib/tire/search/facet.rb +5 -1
- data/lib/tire/search/query.rb +36 -1
- data/lib/tire/search/script_field.rb +23 -0
- data/lib/tire/tasks.rb +1 -1
- data/lib/tire/version.rb +8 -15
- data/test/integration/dis_max_queries_test.rb +68 -0
- data/test/integration/facets_test.rb +27 -0
- data/test/integration/index_store_test.rb +17 -1
- data/test/integration/index_update_document_test.rb +111 -0
- data/test/integration/persistent_model_test.rb +13 -0
- data/test/integration/prefix_query_test.rb +43 -0
- data/test/integration/reindex_test.rb +10 -0
- data/test/integration/script_fields_test.rb +38 -0
- data/test/test_helper.rb +6 -1
- data/test/unit/index_test.rb +58 -5
- data/test/unit/model_persistence_test.rb +14 -2
- data/test/unit/model_search_test.rb +14 -0
- data/test/unit/results_item_test.rb +9 -2
- data/test/unit/rubyext_test.rb +6 -0
- data/test/unit/search_query_test.rb +61 -2
- data/test/unit/search_script_field_test.rb +26 -0
- data/test/unit/search_test.rb +46 -2
- data/tire.gemspec +1 -1
- metadata +62 -53
@@ -0,0 +1,23 @@
|
|
1
|
+
module Tire
|
2
|
+
module Search
|
3
|
+
|
4
|
+
# http://www.elasticsearch.org/guide/reference/api/search/script-fields.html
|
5
|
+
# http://www.elasticsearch.org/guide/reference/modules/scripting.html
|
6
|
+
|
7
|
+
class ScriptField
|
8
|
+
|
9
|
+
def initialize(name, options)
|
10
|
+
@hash = { name => options }
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_json
|
14
|
+
to_hash.to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
@hash
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/tire/tasks.rb
CHANGED
data/lib/tire/version.rb
CHANGED
@@ -1,22 +1,15 @@
|
|
1
1
|
module Tire
|
2
|
-
VERSION = "0.4.
|
2
|
+
VERSION = "0.4.3"
|
3
3
|
|
4
4
|
CHANGELOG =<<-END
|
5
5
|
IMPORTANT CHANGES LATELY:
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
* Added
|
10
|
-
*
|
11
|
-
* Added support for
|
12
|
-
* Added
|
13
|
-
*
|
14
|
-
* Refactorings, fixes, Ruby 1.8 compatibility
|
15
|
-
|
16
|
-
Version 0.4.2
|
17
|
-
-------------
|
18
|
-
* Fixed incorrect handling of PUT requests in the Curb client
|
19
|
-
* Fixed, that blocks passed to `Tire::Index.new` or `Tire.index` losed the scope
|
20
|
-
* Added `Tire::Alias`, interface and DSL to manage aliases as resources
|
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
|
21
14
|
END
|
22
15
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
|
5
|
+
class DisMaxQueriesIntegrationTest < Test::Unit::TestCase
|
6
|
+
include Test::Integration
|
7
|
+
|
8
|
+
context "Dis Max queries" do
|
9
|
+
setup do
|
10
|
+
Tire.index 'dis_max_test' do
|
11
|
+
delete
|
12
|
+
create
|
13
|
+
|
14
|
+
store title: "It's an Albino, Albino, Albino thing!", text: "Albino, albino, albino! Wanna know about albino? ..."
|
15
|
+
store title: "Albino Vampire Monkey Attacks!", text: "The night was just setting in when ..."
|
16
|
+
store title: "Pinky Elephant", text: "An albino walks into a ZOO and ..."
|
17
|
+
refresh
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
teardown do
|
22
|
+
Tire.index('dis_max_test').delete
|
23
|
+
end
|
24
|
+
|
25
|
+
should "boost matches in both fields" do
|
26
|
+
dis_max = Tire.search 'dis_max_test' do
|
27
|
+
query do
|
28
|
+
dis_max do
|
29
|
+
query { string "albino elephant", fields: ['title', 'text'] }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
# p "DisMax:", dis_max.results.map(&:title)
|
34
|
+
|
35
|
+
assert_equal 'Pinky Elephant', dis_max.results.first.title
|
36
|
+
|
37
|
+
# NOTE: This gives exactly the same result as a boolean query:
|
38
|
+
# boolean = Tire.search 'dis_max_test' do
|
39
|
+
# query do
|
40
|
+
# boolean do
|
41
|
+
# should { string "albino", fields: ['title', 'text'] }
|
42
|
+
# should { string "elephant", fields: ['title', 'text'] }
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
# p "Boolean:", boolean.results.map(&:title)
|
47
|
+
end
|
48
|
+
|
49
|
+
should "allow to set multiple queries" do
|
50
|
+
s = Tire.search('articles-test') do
|
51
|
+
query do
|
52
|
+
dis_max do
|
53
|
+
query { term :tags, 'ruby' }
|
54
|
+
query { term :tags, 'python' }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
assert_equal 2, s.results.size
|
60
|
+
assert_equal 'Two', s.results[0].title
|
61
|
+
assert_equal 'One', s.results[1].title
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -59,6 +59,33 @@ module Tire
|
|
59
59
|
assert_equal 1, s.results.facets['tags']['terms'].first['count'].to_i
|
60
60
|
end
|
61
61
|
|
62
|
+
context "terms" do
|
63
|
+
setup do
|
64
|
+
@s = Tire.search('articles-test') do
|
65
|
+
query { string 'tags:ruby' }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
should "return results ordered by term" do
|
70
|
+
@s.facet('tags') { terms :tags }
|
71
|
+
@s.facet('term-ordered-tags') { terms :tags, order: 'term' }
|
72
|
+
|
73
|
+
facets = @s.results.facets
|
74
|
+
# p facets
|
75
|
+
assert_equal 'ruby', facets['tags']['terms'] .first['term']
|
76
|
+
assert_equal 'python', facets['term-ordered-tags']['terms'].first['term']
|
77
|
+
end
|
78
|
+
|
79
|
+
should "return results aggregated over multiple fields" do
|
80
|
+
@s.facet('multiple-fields') { terms ['tags', 'words'] }
|
81
|
+
|
82
|
+
facets = @s.results.facets
|
83
|
+
# p facets
|
84
|
+
assert_equal 4, facets['multiple-fields']['terms'].size
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
62
89
|
context "date histogram" do
|
63
90
|
|
64
91
|
should "return aggregated values for all results" do
|
@@ -22,7 +22,10 @@ module Tire
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
teardown
|
25
|
+
teardown do
|
26
|
+
Tire.index('articles-test-ids').delete
|
27
|
+
Tire.index('articles-test-types').delete
|
28
|
+
end
|
26
29
|
|
27
30
|
should "happen in existing index" do
|
28
31
|
assert Tire.index("articles-test-ids").exists?
|
@@ -40,6 +43,19 @@ module Tire
|
|
40
43
|
|
41
44
|
end
|
42
45
|
|
46
|
+
should "store documents as proper types" do
|
47
|
+
Tire.index 'articles-test-types' do
|
48
|
+
delete
|
49
|
+
create
|
50
|
+
store :type => 'my_type', :title => 'One'
|
51
|
+
refresh
|
52
|
+
end
|
53
|
+
|
54
|
+
s = Tire.search('articles-test-types/my_type') { query { all } }
|
55
|
+
assert_equal 1, s.results.count
|
56
|
+
assert_equal 'my_type', s.results.first.type
|
57
|
+
end
|
58
|
+
|
43
59
|
end
|
44
60
|
|
45
61
|
context "Removing documents from the index" do
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
|
5
|
+
class IndexUpdateDocumentIntegrationTest < Test::Unit::TestCase
|
6
|
+
include Test::Integration
|
7
|
+
|
8
|
+
context "Updating a document" do
|
9
|
+
|
10
|
+
setup do
|
11
|
+
Tire.index 'articles-with-tags' do
|
12
|
+
delete
|
13
|
+
create
|
14
|
+
|
15
|
+
store :type => 'article', :id => 1, :title => 'One', :tags => ['foo'], :views => 0
|
16
|
+
store :type => 'article', :id => 2, :title => 'Two', :tags => ['foo', 'bar'], :views => 10
|
17
|
+
store :type => 'article', :id => 3, :title => 'Three', :tags => ['foobar']
|
18
|
+
|
19
|
+
refresh
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
teardown { Tire.index('articles-with-tags').delete }
|
24
|
+
|
25
|
+
should "increment a counter" do
|
26
|
+
Tire.index('articles-with-tags') { update( 'article', '1', :script => "ctx._source.views += 1" ) and refresh }
|
27
|
+
|
28
|
+
document = Tire.search('articles-with-tags') { query { string 'title:one' } }.results.first
|
29
|
+
assert_equal 1, document.views, document.inspect
|
30
|
+
|
31
|
+
Tire.index('articles-with-tags') { update( 'article', '2', :script => "ctx._source.views += 1" ) and refresh }
|
32
|
+
|
33
|
+
document = Tire.search('articles-with-tags') { query { string 'title:two' } }.results.first
|
34
|
+
assert_equal 11, document.views, document.inspect
|
35
|
+
end
|
36
|
+
|
37
|
+
should "add a tag to document" do
|
38
|
+
Tire.index('articles-with-tags') do
|
39
|
+
update 'article', '1', :script => "ctx._source.tags += tag",
|
40
|
+
:params => { :tag => 'new' }
|
41
|
+
refresh
|
42
|
+
end
|
43
|
+
|
44
|
+
document = Tire.search('articles-with-tags') { query { string 'title:one' } }.results.first
|
45
|
+
assert_equal ['foo', 'new'], document.tags, document.inspect
|
46
|
+
end
|
47
|
+
|
48
|
+
should "remove a tag from document" do
|
49
|
+
Tire.index('articles-with-tags') do
|
50
|
+
update 'article', '1', :script => "ctx._source.tags = tags",
|
51
|
+
:params => { :tags => [] }
|
52
|
+
refresh
|
53
|
+
end
|
54
|
+
|
55
|
+
document = Tire.index('articles-with-tags').retrieve 'article', '1'
|
56
|
+
assert_equal [], document.tags, document.inspect
|
57
|
+
end
|
58
|
+
|
59
|
+
should "remove the entire document if specific condition is met" do
|
60
|
+
Tire.index('articles-with-tags') do
|
61
|
+
# Remove document when it contains tag 'foobar'
|
62
|
+
update 'article', '3', :script => "ctx._source.tags.contains(tag) ? ctx.op = 'delete' : 'none'",
|
63
|
+
:params => { :tag => 'foobar' }
|
64
|
+
refresh
|
65
|
+
end
|
66
|
+
|
67
|
+
assert_nil Tire.index('articles-with-tags').retrieve 'article', '3'
|
68
|
+
end
|
69
|
+
|
70
|
+
should "pass the operation parameters to the API" do
|
71
|
+
Tire.index('articles-with-tags').update 'article', '2', { :script => "ctx._source.tags += tag",
|
72
|
+
:params => { :tag => 'new' }
|
73
|
+
},
|
74
|
+
{
|
75
|
+
:refresh => true
|
76
|
+
}
|
77
|
+
|
78
|
+
document = Tire.search('articles-with-tags') { query { string 'title:two' } }.results.first
|
79
|
+
assert_equal 3, document.tags.size, document.inspect
|
80
|
+
end
|
81
|
+
|
82
|
+
should "access variables from the outer scope" do
|
83
|
+
$t = self
|
84
|
+
|
85
|
+
class Updater
|
86
|
+
@tags = ['foo', 'bar', 'baz']
|
87
|
+
|
88
|
+
def self.perform_update!
|
89
|
+
$t.assert_not_nil @tags
|
90
|
+
|
91
|
+
Tire.index('articles-with-tags') do |index|
|
92
|
+
$t.assert_not_nil @tags
|
93
|
+
|
94
|
+
index.update 'article', '3', :script => "ctx._source.tags = tags",
|
95
|
+
:params => { :tags => @tags }
|
96
|
+
index.refresh
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
Updater.perform_update!
|
102
|
+
|
103
|
+
document = Tire.search('articles-with-tags') { query { string 'title:three' } }.results.first
|
104
|
+
assert_equal 3, document.tags.size, document.inspect
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -50,7 +50,20 @@ module Tire
|
|
50
50
|
results = PersistentArticle.find [1, 2]
|
51
51
|
|
52
52
|
assert_equal 2, results.size
|
53
|
+
end
|
54
|
+
|
55
|
+
should "be persisted" do
|
56
|
+
one = PersistentArticle.create :id => 1, :title => 'One'
|
57
|
+
PersistentArticle.index.refresh
|
58
|
+
|
59
|
+
a = PersistentArticle.all.first
|
60
|
+
assert a.persisted?, a.inspect
|
61
|
+
|
62
|
+
b = PersistentArticle.first
|
63
|
+
assert b.persisted?, b.inspect
|
53
64
|
|
65
|
+
c = PersistentArticle.search { query { string 'one' } }.first
|
66
|
+
assert c.persisted?, c.inspect
|
54
67
|
end
|
55
68
|
|
56
69
|
should "return default values for properties without value" do
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
|
5
|
+
class PrefixQueryTest < Test::Unit::TestCase
|
6
|
+
include Test::Integration
|
7
|
+
|
8
|
+
context "Prefix queries" do
|
9
|
+
|
10
|
+
should "search by a prefix" do
|
11
|
+
s = Tire.search('articles-test') do
|
12
|
+
query do
|
13
|
+
# "on" => "One"
|
14
|
+
prefix :title, "on"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
assert_equal 1, s.results.size
|
19
|
+
assert_equal ['One'], s.results.map(&:title)
|
20
|
+
end
|
21
|
+
|
22
|
+
should "allow to specify boost" do
|
23
|
+
s = Tire.search('articles-test') do
|
24
|
+
query do
|
25
|
+
boolean do
|
26
|
+
# "on" => "One", boost it
|
27
|
+
should { prefix :title, "on", :boost => 2.0 }
|
28
|
+
should { all }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
sort { by :_score }
|
32
|
+
end
|
33
|
+
|
34
|
+
assert_equal 5, s.results.size
|
35
|
+
assert_equal 'One', s.results.first.title
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -39,6 +39,16 @@ module Tire
|
|
39
39
|
assert_equal 2, Tire.search('reindex-test-new').results.total
|
40
40
|
end
|
41
41
|
|
42
|
+
should "transform documents with a passed lambda" do
|
43
|
+
Tire.index('reindex-test').reindex 'reindex-test-new', transform: lambda { |document|
|
44
|
+
document[:title] += 'UPDATED'
|
45
|
+
document
|
46
|
+
}
|
47
|
+
|
48
|
+
Tire.index('reindex-test-new').refresh
|
49
|
+
assert_match /UPDATED/, Tire.search('reindex-test-new').results.first.title
|
50
|
+
end
|
51
|
+
|
42
52
|
end
|
43
53
|
|
44
54
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
|
5
|
+
class ScriptFieldsIntegrationTest < Test::Unit::TestCase
|
6
|
+
include Test::Integration
|
7
|
+
|
8
|
+
context "ScriptFields" do
|
9
|
+
|
10
|
+
should "add multiple fields to the results" do
|
11
|
+
# 1.json > title: "One", words: 125
|
12
|
+
|
13
|
+
s = Tire.search('articles-test') do
|
14
|
+
query { string "One" }
|
15
|
+
script_field :double_words, :script => "doc['words'].value * 2"
|
16
|
+
script_field :triple_words, :script => "doc['words'].value * 3"
|
17
|
+
end
|
18
|
+
|
19
|
+
assert_equal 250, s.results.first.double_words
|
20
|
+
assert_equal 375, s.results.first.triple_words
|
21
|
+
end
|
22
|
+
|
23
|
+
should "allow passing parameters to the script" do
|
24
|
+
# 1.json > title: "One", words: 125
|
25
|
+
|
26
|
+
s = Tire.search('articles-test') do
|
27
|
+
query { string "One" }
|
28
|
+
script_field :double_words, :script => "doc['words'].value * factor", :params => { :factor => 2 }
|
29
|
+
end
|
30
|
+
|
31
|
+
assert_equal 250, s.results.first.double_words
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -8,7 +8,7 @@ require 'yajl/json_gem'
|
|
8
8
|
require 'sqlite3'
|
9
9
|
|
10
10
|
require 'shoulda'
|
11
|
-
require 'turn/autorun' unless ENV["TM_FILEPATH"] ||
|
11
|
+
require 'turn/autorun' unless ENV["TM_FILEPATH"] || defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
|
12
12
|
require 'mocha'
|
13
13
|
|
14
14
|
require 'active_support/core_ext/hash/indifferent_access'
|
@@ -32,6 +32,11 @@ require File.dirname(__FILE__) + '/models/validated_model'
|
|
32
32
|
|
33
33
|
class Test::Unit::TestCase
|
34
34
|
|
35
|
+
def assert_block(message=nil)
|
36
|
+
raise Test::Unit::AssertionFailedError.new(message.to_s) if (! yield)
|
37
|
+
return true
|
38
|
+
end if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
|
39
|
+
|
35
40
|
def mock_response(body, code=200, headers={})
|
36
41
|
Tire::HTTP::Response.new(body, code, headers)
|
37
42
|
end
|