slingshot-rb 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.markdown +276 -50
- data/examples/rails-application-template.rb +144 -0
- data/examples/slingshot-dsl.rb +272 -102
- data/lib/slingshot.rb +13 -0
- data/lib/slingshot/client.rb +10 -1
- data/lib/slingshot/dsl.rb +17 -1
- data/lib/slingshot/index.rb +109 -7
- data/lib/slingshot/model/callbacks.rb +23 -0
- data/lib/slingshot/model/import.rb +18 -0
- data/lib/slingshot/model/indexing.rb +50 -0
- data/lib/slingshot/model/naming.rb +30 -0
- data/lib/slingshot/model/persistence.rb +34 -0
- data/lib/slingshot/model/persistence/attributes.rb +60 -0
- data/lib/slingshot/model/persistence/finders.rb +61 -0
- data/lib/slingshot/model/persistence/storage.rb +75 -0
- data/lib/slingshot/model/search.rb +97 -0
- data/lib/slingshot/results/collection.rb +35 -10
- data/lib/slingshot/results/item.rb +10 -7
- data/lib/slingshot/results/pagination.rb +30 -0
- data/lib/slingshot/rubyext/symbol.rb +11 -0
- data/lib/slingshot/search.rb +3 -2
- data/lib/slingshot/search/facet.rb +8 -6
- data/lib/slingshot/search/filter.rb +7 -8
- data/lib/slingshot/search/highlight.rb +1 -3
- data/lib/slingshot/search/query.rb +4 -0
- data/lib/slingshot/search/sort.rb +5 -0
- data/lib/slingshot/tasks.rb +88 -0
- data/lib/slingshot/version.rb +1 -1
- data/slingshot.gemspec +17 -4
- data/test/integration/active_model_searchable_test.rb +80 -0
- data/test/integration/active_record_searchable_test.rb +193 -0
- data/test/integration/highlight_test.rb +1 -1
- data/test/integration/index_mapping_test.rb +1 -1
- data/test/integration/index_store_test.rb +27 -0
- data/test/integration/persistent_model_test.rb +35 -0
- data/test/integration/query_string_test.rb +3 -3
- data/test/integration/sort_test.rb +2 -2
- data/test/models/active_model_article.rb +31 -0
- data/test/models/active_model_article_with_callbacks.rb +49 -0
- data/test/models/active_model_article_with_custom_index_name.rb +5 -0
- data/test/models/active_record_article.rb +12 -0
- data/test/models/persistent_article.rb +11 -0
- data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
- data/test/models/supermodel_article.rb +22 -0
- data/test/models/validated_model.rb +11 -0
- data/test/test_helper.rb +4 -0
- data/test/unit/active_model_lint_test.rb +17 -0
- data/test/unit/client_test.rb +4 -0
- data/test/unit/configuration_test.rb +4 -0
- data/test/unit/index_test.rb +240 -17
- data/test/unit/model_callbacks_test.rb +90 -0
- data/test/unit/model_import_test.rb +71 -0
- data/test/unit/model_persistence_test.rb +400 -0
- data/test/unit/model_search_test.rb +289 -0
- data/test/unit/results_collection_test.rb +69 -7
- data/test/unit/results_item_test.rb +8 -14
- data/test/unit/rubyext_hash_test.rb +19 -0
- data/test/unit/search_facet_test.rb +25 -7
- data/test/unit/search_filter_test.rb +3 -0
- data/test/unit/search_query_test.rb +11 -0
- data/test/unit/search_sort_test.rb +8 -0
- data/test/unit/search_test.rb +14 -0
- data/test/unit/slingshot_test.rb +38 -0
- metadata +133 -26
@@ -21,7 +21,7 @@ module Slingshot
|
|
21
21
|
assert doc.highlight.title.to_s.include?('<em>'), "Highlight does not include default highlight tag"
|
22
22
|
end
|
23
23
|
|
24
|
-
should "return entire content with highlighted fragments
|
24
|
+
should "return entire content with highlighted fragments" do
|
25
25
|
# Slingshot::Configuration.logger STDERR, :level => 'debug'
|
26
26
|
|
27
27
|
content = "A Fox one day fell into a deep well and could find no means of escape. A Goat, overcome with thirst, came to the same well, and seeing the Fox, inquired if the water was good. Concealing his sad plight under a merry guise, the Fox indulged in a lavish praise of the water, saying it was excellent beyond measure, and encouraging him to descend. The Goat, mindful only of his thirst, thoughtlessly jumped down, but just as he drank, the Fox informed him of the difficulty they were both in and suggested a scheme for their common escape. \"If,\" said he, \"you will place your forefeet upon the wall and bend your head, I will run up your back and escape, and will help you out afterwards.\" The Goat readily assented and the Fox leaped upon his back. Steadying himself with the Goat horns, he safely reached the mouth of the well and made off as fast as he could. When the Goat upbraided him for breaking his promise, he turned around and cried out, \"You foolish old fellow! If you had as many brains in your head as you have hairs in your beard, you would never have gone down before you had inspected the way up, nor have exposed yourself to dangers from which you had no means of escape.\" Look before you leap."
|
@@ -15,7 +15,7 @@ module Slingshot
|
|
15
15
|
store :article, :title => 'One'
|
16
16
|
refresh
|
17
17
|
end
|
18
|
-
sleep 1
|
18
|
+
sleep 1.5
|
19
19
|
|
20
20
|
assert_equal 'string', index.mapping['article']['properties']['title']['type'], index.mapping.inspect
|
21
21
|
assert_nil index.mapping['article']['properties']['title']['boost'], index.mapping.inspect
|
@@ -36,6 +36,33 @@ module Slingshot
|
|
36
36
|
|
37
37
|
end
|
38
38
|
|
39
|
+
context "Removing documents from the index" do
|
40
|
+
|
41
|
+
teardown { Slingshot.index('articles-test-remove').delete }
|
42
|
+
|
43
|
+
setup do
|
44
|
+
Slingshot.index 'articles-test-remove' do
|
45
|
+
delete
|
46
|
+
create
|
47
|
+
store :id => 1, :title => 'One'
|
48
|
+
store :id => 2, :title => 'Two'
|
49
|
+
refresh
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
should "remove document from the index" do
|
54
|
+
|
55
|
+
assert_equal 2, Slingshot.search('articles-test-remove') { query { string '*' } }.results.count
|
56
|
+
|
57
|
+
assert_nothing_raised do
|
58
|
+
assert Slingshot.index('articles-test-remove').remove 1
|
59
|
+
assert ! Slingshot.index('articles-test-remove').remove(1)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
39
66
|
end
|
40
67
|
|
41
68
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Slingshot
|
4
|
+
|
5
|
+
class PersistentModelIntegrationTest < Test::Unit::TestCase
|
6
|
+
include Test::Integration
|
7
|
+
|
8
|
+
def setup
|
9
|
+
super
|
10
|
+
PersistentArticle.index.delete
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
super
|
15
|
+
PersistentArticle.index.delete
|
16
|
+
end
|
17
|
+
|
18
|
+
context "PersistentModel" do
|
19
|
+
|
20
|
+
should "save documents into index and find them by IDs" do
|
21
|
+
one = PersistentArticle.create :id => 1, :title => 'One'
|
22
|
+
two = PersistentArticle.create :id => 2, :title => 'Two'
|
23
|
+
|
24
|
+
PersistentArticle.index.refresh
|
25
|
+
sleep(1.5)
|
26
|
+
|
27
|
+
results = PersistentArticle.find [1, 2]
|
28
|
+
|
29
|
+
assert_equal 2, results.size
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -10,19 +10,19 @@ module Slingshot
|
|
10
10
|
should "find article by title" do
|
11
11
|
q = 'title:one'
|
12
12
|
assert_equal 1, search(q).results.count
|
13
|
-
assert_equal 'One', search(q).results.first[
|
13
|
+
assert_equal 'One', search(q).results.first[:title]
|
14
14
|
end
|
15
15
|
|
16
16
|
should "find articles by title with boosting" do
|
17
17
|
q = 'title:one^100 OR title:two'
|
18
18
|
assert_equal 2, search(q).results.count
|
19
|
-
assert_equal 'One', search(q).results.first[
|
19
|
+
assert_equal 'One', search(q).results.first[:title]
|
20
20
|
end
|
21
21
|
|
22
22
|
should "find articles by tags" do
|
23
23
|
q = 'tags:ruby AND tags:python'
|
24
24
|
assert_equal 1, search(q).results.count
|
25
|
-
assert_equal 'Two', search(q).results.first[
|
25
|
+
assert_equal 'Two', search(q).results.first[:title]
|
26
26
|
end
|
27
27
|
|
28
28
|
should "find any article with tags" do
|
@@ -15,7 +15,7 @@ module Slingshot
|
|
15
15
|
end
|
16
16
|
|
17
17
|
assert_equal 5, s.results.count
|
18
|
-
assert_equal 'Five', s.results.first[
|
18
|
+
assert_equal 'Five', s.results.first[:title]
|
19
19
|
end
|
20
20
|
|
21
21
|
should "sort by title, descending" do
|
@@ -26,7 +26,7 @@ module Slingshot
|
|
26
26
|
end
|
27
27
|
|
28
28
|
assert_equal 5, s.results.count
|
29
|
-
assert_equal 'Two', s.results.first[
|
29
|
+
assert_equal 'Two', s.results.first[:title]
|
30
30
|
end
|
31
31
|
|
32
32
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Example ActiveModel class
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'active_model'
|
5
|
+
|
6
|
+
class ActiveModelArticle
|
7
|
+
|
8
|
+
extend ActiveModel::Naming
|
9
|
+
include ActiveModel::AttributeMethods
|
10
|
+
include ActiveModel::Serialization
|
11
|
+
include ActiveModel::Serializers::JSON
|
12
|
+
|
13
|
+
include Slingshot::Model::Search
|
14
|
+
|
15
|
+
attr_reader :attributes
|
16
|
+
|
17
|
+
def initialize(attributes = {})
|
18
|
+
@attributes = attributes
|
19
|
+
end
|
20
|
+
|
21
|
+
def id; attributes['id'] || attributes['_id']; end
|
22
|
+
def id=(value); attributes['id'] = value; end
|
23
|
+
|
24
|
+
def method_missing(name, *args, &block)
|
25
|
+
attributes[name.to_sym] || attributes[name.to_s] || super
|
26
|
+
end
|
27
|
+
|
28
|
+
def persisted?; true; end
|
29
|
+
def save; true; end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Example ActiveModel class with callbacks
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'active_model'
|
5
|
+
|
6
|
+
class ActiveModelArticleWithCallbacks
|
7
|
+
|
8
|
+
include ActiveModel::AttributeMethods
|
9
|
+
include ActiveModel::Validations
|
10
|
+
include ActiveModel::Serialization
|
11
|
+
include ActiveModel::Serializers::JSON
|
12
|
+
include ActiveModel::Naming
|
13
|
+
|
14
|
+
extend ActiveModel::Callbacks
|
15
|
+
define_model_callbacks :save, :destroy
|
16
|
+
|
17
|
+
include Slingshot::Model::Search
|
18
|
+
include Slingshot::Model::Callbacks
|
19
|
+
|
20
|
+
attr_reader :attributes
|
21
|
+
|
22
|
+
def initialize(attributes = {})
|
23
|
+
@attributes = attributes
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(id, *args, &block)
|
27
|
+
attributes[id.to_sym] || attributes[id.to_s] || super
|
28
|
+
end
|
29
|
+
|
30
|
+
def persisted?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def save
|
35
|
+
_run_save_callbacks do
|
36
|
+
STDERR.puts "[Saving ...]"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def destroy
|
41
|
+
_run_destroy_callbacks do
|
42
|
+
STDERR.puts "[Destroying ...]"
|
43
|
+
@destroyed = true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def destroyed?; !!@destroyed; end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
class ActiveRecordArticle < ActiveRecord::Base
|
5
|
+
include Slingshot::Model::Search
|
6
|
+
include Slingshot::Model::Callbacks
|
7
|
+
|
8
|
+
mapping do
|
9
|
+
indexes :title, :type => 'string', :boost => 10, :analyzer => 'snowball'
|
10
|
+
indexes :created_at, :type => 'date'
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Example ActiveModel class for testing :searchable mode
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'supermodel'
|
5
|
+
|
6
|
+
class SupermodelArticle < SuperModel::Base
|
7
|
+
include SuperModel::RandomID
|
8
|
+
|
9
|
+
include Slingshot::Model::Search
|
10
|
+
include Slingshot::Model::Callbacks
|
11
|
+
|
12
|
+
mapping do
|
13
|
+
indexes :title, :type => 'string', :boost => 15, :analyzer => 'czech'
|
14
|
+
end
|
15
|
+
|
16
|
+
alias :persisted? :exists?
|
17
|
+
|
18
|
+
def destroyed?
|
19
|
+
!self.class.find(self.id) rescue true
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -11,6 +11,10 @@ Dir[File.dirname(__FILE__) + '/models/**/*.rb'].each { |m| require m }
|
|
11
11
|
|
12
12
|
class Test::Unit::TestCase
|
13
13
|
|
14
|
+
def mock_response(body, code=200)
|
15
|
+
stub(:body => body, :code => code)
|
16
|
+
end
|
17
|
+
|
14
18
|
def fixtures_path
|
15
19
|
Pathname( File.expand_path( 'fixtures', File.dirname(__FILE__) ) )
|
16
20
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Slingshot
|
4
|
+
module Model
|
5
|
+
|
6
|
+
class ActiveModelLintTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
include ActiveModel::Lint::Tests
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@model = PersistentArticle.new :title => 'Test'
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/test/unit/client_test.rb
CHANGED
@@ -15,6 +15,9 @@ module Slingshot
|
|
15
15
|
assert_raise(ArgumentError) { @http.post 'URL' }
|
16
16
|
assert_raise(NoMethodError) { @http.post 'URL', 'DATA' }
|
17
17
|
|
18
|
+
assert_raise(ArgumentError) { @http.put }
|
19
|
+
assert_raise(ArgumentError) { @http.put 'URL' }
|
20
|
+
|
18
21
|
assert_raise(ArgumentError) { @http.delete }
|
19
22
|
assert_raise(NoMethodError) { @http.delete 'URL' }
|
20
23
|
end
|
@@ -29,6 +32,7 @@ module Slingshot
|
|
29
32
|
should "respond to HTTP methods" do
|
30
33
|
assert_respond_to Client::RestClient, :get
|
31
34
|
assert_respond_to Client::RestClient, :post
|
35
|
+
assert_respond_to Client::RestClient, :put
|
32
36
|
assert_respond_to Client::RestClient, :delete
|
33
37
|
end
|
34
38
|
|
data/test/unit/index_test.rb
CHANGED
@@ -10,8 +10,22 @@ module Slingshot
|
|
10
10
|
@index = Slingshot::Index.new 'dummy'
|
11
11
|
end
|
12
12
|
|
13
|
+
should "have a name" do
|
14
|
+
assert_equal 'dummy', @index.name
|
15
|
+
end
|
16
|
+
|
17
|
+
should "return true when exists" do
|
18
|
+
Configuration.client.expects(:get).returns(mock_response('{"dummy":{"document":{"properties":{}}}}'))
|
19
|
+
assert @index.exists?
|
20
|
+
end
|
21
|
+
|
22
|
+
should "return false when does not exist" do
|
23
|
+
Configuration.client.expects(:get).raises(RestClient::ResourceNotFound)
|
24
|
+
assert ! @index.exists?
|
25
|
+
end
|
26
|
+
|
13
27
|
should "create new index" do
|
14
|
-
Configuration.client.expects(:post).returns('{"ok":true,"acknowledged":true}')
|
28
|
+
Configuration.client.expects(:post).returns(mock_response('{"ok":true,"acknowledged":true}'))
|
15
29
|
assert @index.create
|
16
30
|
end
|
17
31
|
|
@@ -21,26 +35,26 @@ module Slingshot
|
|
21
35
|
end
|
22
36
|
|
23
37
|
should "delete index" do
|
24
|
-
Configuration.client.expects(:delete).returns('{"ok":true,"acknowledged":true}')
|
38
|
+
Configuration.client.expects(:delete).returns(mock_response('{"ok":true,"acknowledged":true}'))
|
25
39
|
assert @index.delete
|
26
40
|
end
|
27
41
|
|
28
42
|
should "not raise exception and just return false when deleting non-existing index" do
|
29
|
-
Configuration.client.expects(:delete).returns('{"error":"[articles] missing"}')
|
43
|
+
Configuration.client.expects(:delete).returns(mock_response('{"error":"[articles] missing"}'))
|
30
44
|
assert_nothing_raised { assert ! @index.delete }
|
31
45
|
Configuration.client.expects(:delete).raises(RestClient::BadRequest)
|
32
46
|
assert_nothing_raised { assert ! @index.delete }
|
33
47
|
end
|
34
48
|
|
35
49
|
should "refresh the index" do
|
36
|
-
Configuration.client.expects(:post).returns('{"ok":true,"_shards":{}}')
|
50
|
+
Configuration.client.expects(:post).returns(mock_response('{"ok":true,"_shards":{}}'))
|
37
51
|
assert_nothing_raised { assert @index.refresh }
|
38
52
|
end
|
39
53
|
|
40
54
|
context "mapping" do
|
41
55
|
|
42
56
|
should "create index with mapping" do
|
43
|
-
Configuration.client.expects(:post).returns('{"ok":true,"acknowledged":true}')
|
57
|
+
Configuration.client.expects(:post).returns(mock_response('{"ok":true,"acknowledged":true}'))
|
44
58
|
|
45
59
|
assert @index.create :settings => { :number_of_shards => 1 },
|
46
60
|
:mappings => { :article => {
|
@@ -67,7 +81,7 @@ module Slingshot
|
|
67
81
|
}
|
68
82
|
}
|
69
83
|
JSON
|
70
|
-
Configuration.client.stubs(:get).returns(json)
|
84
|
+
Configuration.client.stubs(:get).returns(mock_response(json))
|
71
85
|
|
72
86
|
assert_equal 'string', @index.mapping['article']['properties']['title']['type']
|
73
87
|
assert_equal 2.0, @index.mapping['article']['properties']['title']['boost']
|
@@ -78,19 +92,19 @@ module Slingshot
|
|
78
92
|
context "when storing" do
|
79
93
|
|
80
94
|
should "properly set type from args" do
|
81
|
-
Configuration.client.expects(:post).with("#{Configuration.url}/dummy/article/", '{"title":"Test"}').returns('{"ok":true,"_id":"test"}').twice
|
95
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/dummy/article/", '{"title":"Test"}').returns(mock_response('{"ok":true,"_id":"test"}')).twice
|
82
96
|
@index.store 'article', :title => 'Test'
|
83
97
|
@index.store :article, :title => 'Test'
|
84
98
|
end
|
85
99
|
|
86
100
|
should "set default type" do
|
87
|
-
Configuration.client.expects(:post).with("#{Configuration.url}/dummy/document/", '{"title":"Test"}').returns('{"ok":true,"_id":"test"}')
|
101
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/dummy/document/", '{"title":"Test"}').returns(mock_response('{"ok":true,"_id":"test"}'))
|
88
102
|
@index.store :title => 'Test'
|
89
103
|
end
|
90
104
|
|
91
105
|
should "call #to_indexed_json on non-String documents" do
|
92
106
|
document = { :title => 'Test' }
|
93
|
-
Configuration.client.expects(:post).returns('{"ok":true,"_id":"test"}')
|
107
|
+
Configuration.client.expects(:post).returns(mock_response('{"ok":true,"_id":"test"}'))
|
94
108
|
document.expects(:to_indexed_json)
|
95
109
|
@index.store document
|
96
110
|
end
|
@@ -104,15 +118,15 @@ module Slingshot
|
|
104
118
|
|
105
119
|
should "store Hash it under its ID property" do
|
106
120
|
Configuration.client.expects(:post).with("#{Configuration.url}/dummy/document/123",
|
107
|
-
{:id => 123, :title => 'Test'}
|
108
|
-
returns('{"ok":true,"_id":"123"}')
|
121
|
+
Yajl::Encoder.encode({:id => 123, :title => 'Test'})).
|
122
|
+
returns(mock_response('{"ok":true,"_id":"123"}'))
|
109
123
|
@index.store :id => 123, :title => 'Test'
|
110
124
|
end
|
111
125
|
|
112
126
|
should "store a custom class under its ID property" do
|
113
127
|
Configuration.client.expects(:post).with("#{Configuration.url}/dummy/document/123",
|
114
128
|
{:id => 123, :title => 'Test', :body => 'Lorem'}.to_json).
|
115
|
-
returns('{"ok":true,"_id":"123"}')
|
129
|
+
returns(mock_response('{"ok":true,"_id":"123"}'))
|
116
130
|
@index.store Article.new(:id => 123, :title => 'Test', :body => 'Lorem')
|
117
131
|
end
|
118
132
|
|
@@ -126,24 +140,24 @@ module Slingshot
|
|
126
140
|
Configuration.reset :wrapper
|
127
141
|
|
128
142
|
Configuration.client.stubs(:post).with("#{Configuration.url}/dummy/article/", '{"title":"Test"}').
|
129
|
-
returns('{"ok":true,"_id":"id-1"}')
|
143
|
+
returns(mock_response('{"ok":true,"_id":"id-1"}'))
|
130
144
|
@index.store :article, :title => 'Test'
|
131
145
|
end
|
132
146
|
|
133
147
|
should "return document in default wrapper" do
|
134
148
|
Configuration.client.expects(:get).with("#{Configuration.url}/dummy/article/id-1").
|
135
|
-
returns('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}')
|
149
|
+
returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
|
136
150
|
article = @index.retrieve :article, 'id-1'
|
137
151
|
assert_instance_of Results::Item, article
|
138
|
-
assert_equal 'Test', article['_source']['title']
|
139
152
|
assert_equal 'Test', article.title
|
153
|
+
assert_equal 'Test', article[:title]
|
140
154
|
end
|
141
155
|
|
142
156
|
should "return document as a hash" do
|
143
157
|
Configuration.wrapper Hash
|
144
158
|
|
145
159
|
Configuration.client.expects(:get).with("#{Configuration.url}/dummy/article/id-1").
|
146
|
-
returns('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}')
|
160
|
+
returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
|
147
161
|
article = @index.retrieve :article, 'id-1'
|
148
162
|
assert_instance_of Hash, article
|
149
163
|
end
|
@@ -152,7 +166,7 @@ module Slingshot
|
|
152
166
|
Configuration.wrapper Article
|
153
167
|
|
154
168
|
Configuration.client.expects(:get).with("#{Configuration.url}/dummy/article/id-1").
|
155
|
-
returns('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}')
|
169
|
+
returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
|
156
170
|
article = @index.retrieve :article, 'id-1'
|
157
171
|
assert_instance_of Article, article
|
158
172
|
assert_equal 'Test', article.title
|
@@ -160,6 +174,215 @@ module Slingshot
|
|
160
174
|
|
161
175
|
end
|
162
176
|
|
177
|
+
context "when removing" do
|
178
|
+
|
179
|
+
should "properly set type from args" do
|
180
|
+
Configuration.client.expects(:delete).with("#{Configuration.url}/dummy/article/").
|
181
|
+
returns('{"ok":true,"_id":"test"}').twice
|
182
|
+
@index.remove 'article', :title => 'Test'
|
183
|
+
@index.remove :article, :title => 'Test'
|
184
|
+
end
|
185
|
+
|
186
|
+
should "set default type" do
|
187
|
+
Configuration.client.expects(:delete).with("#{Configuration.url}/dummy/document/").
|
188
|
+
returns('{"ok":true,"_id":"test"}')
|
189
|
+
@index.remove :title => 'Test'
|
190
|
+
end
|
191
|
+
|
192
|
+
should "get ID from hash" do
|
193
|
+
Configuration.client.expects(:delete).with("#{Configuration.url}/dummy/document/1").
|
194
|
+
returns('{"ok":true,"_id":"1"}')
|
195
|
+
@index.remove :id => 1
|
196
|
+
end
|
197
|
+
|
198
|
+
should "get ID from method" do
|
199
|
+
document = stub('document', :id => 1)
|
200
|
+
Configuration.client.expects(:delete).with("#{Configuration.url}/dummy/document/1").
|
201
|
+
returns('{"ok":true,"_id":"1"}')
|
202
|
+
@index.remove document
|
203
|
+
end
|
204
|
+
|
205
|
+
should "get ID from arguments" do
|
206
|
+
Configuration.client.expects(:delete).with("#{Configuration.url}/dummy/document/1").
|
207
|
+
returns('{"ok":true,"_id":"1"}')
|
208
|
+
@index.remove :document, 1
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
context "when storing in bulk" do
|
214
|
+
# The expected JSON looks like this:
|
215
|
+
#
|
216
|
+
# {"index":{"_index":"dummy","_type":"document","_id":"1"}}
|
217
|
+
# {"id":"1","title":"One"}
|
218
|
+
# {"index":{"_index":"dummy","_type":"document","_id":"2"}}
|
219
|
+
# {"id":"2","title":"Two"}
|
220
|
+
#
|
221
|
+
# See http://www.elasticsearch.org/guide/reference/api/bulk.html
|
222
|
+
|
223
|
+
should "serialize Hashes" do
|
224
|
+
Configuration.client.expects(:post).with do |url, json|
|
225
|
+
url == "#{Configuration.url}/_bulk"
|
226
|
+
json =~ /"_index":"dummy"/
|
227
|
+
json =~ /"_type":"document"/
|
228
|
+
json =~ /"_id":"1"/
|
229
|
+
json =~ /"_id":"2"/
|
230
|
+
json =~ /"id":"1"/
|
231
|
+
json =~ /"id":"2"/
|
232
|
+
json =~ /"title":"One"/
|
233
|
+
json =~ /"title":"Two"/
|
234
|
+
end.returns('{}')
|
235
|
+
|
236
|
+
@index.bulk_store [ {:id => '1', :title => 'One'}, {:id => '2', :title => 'Two'} ]
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
should "serialize ActiveModel instances" do
|
241
|
+
Configuration.client.expects(:post).with do |url, json|
|
242
|
+
url == "#{Configuration.url}/_bulk"
|
243
|
+
json =~ /"_index":"active_model_articles"/
|
244
|
+
json =~ /"_type":"article"/
|
245
|
+
json =~ /"_id":"1"/
|
246
|
+
json =~ /"_id":"2"/
|
247
|
+
json =~ /"id":"1"/
|
248
|
+
json =~ /"id":"2"/
|
249
|
+
json =~ /"title":"One"/
|
250
|
+
json =~ /"title":"Two"/
|
251
|
+
end.returns('{}')
|
252
|
+
|
253
|
+
one = ActiveModelArticle.new 'title' => 'One'; one.id = '1'
|
254
|
+
two = ActiveModelArticle.new 'title' => 'Two'; two.id = '2'
|
255
|
+
|
256
|
+
ActiveModelArticle.index.bulk_store [ one, two ]
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
should "try again when an exception occurs" do
|
261
|
+
Configuration.client.expects(:post).raises(RestClient::RequestFailed).at_least(2)
|
262
|
+
|
263
|
+
assert_raise(RestClient::RequestFailed) do
|
264
|
+
@index.bulk_store [ {:id => '1', :title => 'One'}, {:id => '2', :title => 'Two'} ]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
should_eventually "raise exception when collection item does not have ID" do
|
269
|
+
# TODO: raise exception when collection item does not have ID
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
274
|
+
context "when importing" do
|
275
|
+
setup do
|
276
|
+
@index = Slingshot::Index.new 'import'
|
277
|
+
end
|
278
|
+
|
279
|
+
class ::ImportData
|
280
|
+
DATA = (1..4).to_a
|
281
|
+
|
282
|
+
def self.paginate(options={})
|
283
|
+
options = {:page => 1, :per_page => 1000}.update options
|
284
|
+
DATA.slice( (options[:page]-1)*options[:per_page]...options[:page]*options[:per_page] )
|
285
|
+
end
|
286
|
+
|
287
|
+
def self.each(&block); DATA.each █ end
|
288
|
+
def self.map(&block); DATA.map █ end
|
289
|
+
def self.count; DATA.size; end
|
290
|
+
end
|
291
|
+
|
292
|
+
should "be initialized with a collection" do
|
293
|
+
@index.expects(:bulk_store).returns(:true)
|
294
|
+
|
295
|
+
assert_nothing_raised { @index.import [{ :id => 1, :title => 'Article' }] }
|
296
|
+
end
|
297
|
+
|
298
|
+
should "be initialized with a class and params" do
|
299
|
+
@index.expects(:bulk_store).returns(:true)
|
300
|
+
|
301
|
+
assert_nothing_raised { @index.import ImportData }
|
302
|
+
end
|
303
|
+
|
304
|
+
context "plain collection" do
|
305
|
+
|
306
|
+
should "just store it in bulk" do
|
307
|
+
collection = [{ :id => 1, :title => 'Article' }]
|
308
|
+
@index.expects(:bulk_store).with( collection ).returns(true)
|
309
|
+
|
310
|
+
@index.import collection
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
314
|
+
|
315
|
+
context "class" do
|
316
|
+
|
317
|
+
should "call the passed method and bulk store the results" do
|
318
|
+
@index.expects(:bulk_store).with([1, 2, 3, 4]).returns(true)
|
319
|
+
|
320
|
+
@index.import ImportData, :paginate
|
321
|
+
end
|
322
|
+
|
323
|
+
should "pass the params to the passed method and bulk store the results" do
|
324
|
+
@index.expects(:bulk_store).with([1, 2]).returns(true)
|
325
|
+
@index.expects(:bulk_store).with([3, 4]).returns(true)
|
326
|
+
|
327
|
+
@index.import ImportData, :paginate, :page => 1, :per_page => 2
|
328
|
+
end
|
329
|
+
|
330
|
+
should "pass the class when method not passed" do
|
331
|
+
@index.expects(:bulk_store).with(ImportData).returns(true)
|
332
|
+
|
333
|
+
@index.import ImportData
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
context "with passed block" do
|
339
|
+
|
340
|
+
context "and plain collection" do
|
341
|
+
|
342
|
+
should "allow to manipulate the collection in the block" do
|
343
|
+
Slingshot::Index.any_instance.expects(:bulk_store).with([{ :id => 1, :title => 'ARTICLE' }])
|
344
|
+
|
345
|
+
|
346
|
+
@index.import [{ :id => 1, :title => 'Article' }] do |articles|
|
347
|
+
articles.map { |article| article.update :title => article[:title].upcase }
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
end
|
352
|
+
|
353
|
+
context "and object" do
|
354
|
+
|
355
|
+
should "call the passed block on every batch" do
|
356
|
+
Slingshot::Index.any_instance.expects(:bulk_store).with([1, 2])
|
357
|
+
Slingshot::Index.any_instance.expects(:bulk_store).with([3, 4])
|
358
|
+
|
359
|
+
runs = 0
|
360
|
+
@index.import ImportData, :paginate, :per_page => 2 do |documents|
|
361
|
+
runs += 1
|
362
|
+
# Don't forget to return the documents at the end of the block
|
363
|
+
documents
|
364
|
+
end
|
365
|
+
|
366
|
+
assert_equal 2, runs
|
367
|
+
end
|
368
|
+
|
369
|
+
should "allow to manipulate the documents in passed block" do
|
370
|
+
Slingshot::Index.any_instance.expects(:bulk_store).with([2, 3])
|
371
|
+
Slingshot::Index.any_instance.expects(:bulk_store).with([4, 5])
|
372
|
+
|
373
|
+
|
374
|
+
@index.import ImportData, :paginate, :per_page => 2 do |documents|
|
375
|
+
# Add 1 to every "document" and return them
|
376
|
+
documents.map { |d| d + 1 }
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
end
|
381
|
+
|
382
|
+
end
|
383
|
+
|
384
|
+
end
|
385
|
+
|
163
386
|
end
|
164
387
|
|
165
388
|
end
|