load_balanced_tire 0.1
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/.gitignore +14 -0
- data/.travis.yml +29 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +760 -0
- data/Rakefile +78 -0
- data/examples/rails-application-template.rb +249 -0
- data/examples/tire-dsl.rb +876 -0
- data/lib/tire.rb +55 -0
- data/lib/tire/alias.rb +296 -0
- data/lib/tire/configuration.rb +30 -0
- data/lib/tire/dsl.rb +43 -0
- data/lib/tire/http/client.rb +62 -0
- data/lib/tire/http/clients/curb.rb +61 -0
- data/lib/tire/http/clients/faraday.rb +71 -0
- data/lib/tire/http/response.rb +27 -0
- data/lib/tire/index.rb +361 -0
- data/lib/tire/logger.rb +60 -0
- data/lib/tire/model/callbacks.rb +40 -0
- data/lib/tire/model/import.rb +26 -0
- data/lib/tire/model/indexing.rb +128 -0
- data/lib/tire/model/naming.rb +100 -0
- data/lib/tire/model/percolate.rb +99 -0
- data/lib/tire/model/persistence.rb +71 -0
- data/lib/tire/model/persistence/attributes.rb +143 -0
- data/lib/tire/model/persistence/finders.rb +66 -0
- data/lib/tire/model/persistence/storage.rb +69 -0
- data/lib/tire/model/search.rb +307 -0
- data/lib/tire/results/collection.rb +114 -0
- data/lib/tire/results/item.rb +86 -0
- data/lib/tire/results/pagination.rb +54 -0
- data/lib/tire/rubyext/hash.rb +8 -0
- data/lib/tire/rubyext/ruby_1_8.rb +7 -0
- data/lib/tire/rubyext/symbol.rb +11 -0
- data/lib/tire/search.rb +188 -0
- data/lib/tire/search/facet.rb +74 -0
- data/lib/tire/search/filter.rb +28 -0
- data/lib/tire/search/highlight.rb +37 -0
- data/lib/tire/search/query.rb +186 -0
- data/lib/tire/search/scan.rb +114 -0
- data/lib/tire/search/script_field.rb +23 -0
- data/lib/tire/search/sort.rb +25 -0
- data/lib/tire/tasks.rb +135 -0
- data/lib/tire/utils.rb +17 -0
- data/lib/tire/version.rb +22 -0
- data/test/fixtures/articles/1.json +1 -0
- data/test/fixtures/articles/2.json +1 -0
- data/test/fixtures/articles/3.json +1 -0
- data/test/fixtures/articles/4.json +1 -0
- data/test/fixtures/articles/5.json +1 -0
- data/test/integration/active_model_indexing_test.rb +51 -0
- data/test/integration/active_model_searchable_test.rb +114 -0
- data/test/integration/active_record_searchable_test.rb +446 -0
- data/test/integration/boolean_queries_test.rb +43 -0
- data/test/integration/count_test.rb +34 -0
- data/test/integration/custom_score_queries_test.rb +88 -0
- data/test/integration/dis_max_queries_test.rb +68 -0
- data/test/integration/dsl_search_test.rb +22 -0
- data/test/integration/explanation_test.rb +44 -0
- data/test/integration/facets_test.rb +259 -0
- data/test/integration/filtered_queries_test.rb +66 -0
- data/test/integration/filters_test.rb +63 -0
- data/test/integration/fuzzy_queries_test.rb +20 -0
- data/test/integration/highlight_test.rb +64 -0
- data/test/integration/index_aliases_test.rb +122 -0
- data/test/integration/index_mapping_test.rb +43 -0
- data/test/integration/index_store_test.rb +96 -0
- data/test/integration/index_update_document_test.rb +111 -0
- data/test/integration/mongoid_searchable_test.rb +309 -0
- data/test/integration/percolator_test.rb +111 -0
- data/test/integration/persistent_model_test.rb +130 -0
- data/test/integration/prefix_query_test.rb +43 -0
- data/test/integration/query_return_version_test.rb +70 -0
- data/test/integration/query_string_test.rb +52 -0
- data/test/integration/range_queries_test.rb +36 -0
- data/test/integration/reindex_test.rb +46 -0
- data/test/integration/results_test.rb +39 -0
- data/test/integration/scan_test.rb +56 -0
- data/test/integration/script_fields_test.rb +38 -0
- data/test/integration/sort_test.rb +36 -0
- data/test/integration/text_query_test.rb +39 -0
- 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_document_type.rb +7 -0
- data/test/models/active_model_article_with_custom_index_name.rb +7 -0
- data/test/models/active_record_models.rb +122 -0
- data/test/models/article.rb +15 -0
- data/test/models/mongoid_models.rb +97 -0
- data/test/models/persistent_article.rb +11 -0
- data/test/models/persistent_article_in_namespace.rb +12 -0
- data/test/models/persistent_article_with_casting.rb +28 -0
- data/test/models/persistent_article_with_defaults.rb +11 -0
- data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
- data/test/models/supermodel_article.rb +17 -0
- data/test/models/validated_model.rb +11 -0
- data/test/test_helper.rb +93 -0
- data/test/unit/active_model_lint_test.rb +17 -0
- data/test/unit/configuration_test.rb +74 -0
- data/test/unit/http_client_test.rb +76 -0
- data/test/unit/http_response_test.rb +49 -0
- data/test/unit/index_alias_test.rb +275 -0
- data/test/unit/index_test.rb +894 -0
- data/test/unit/logger_test.rb +125 -0
- data/test/unit/model_callbacks_test.rb +116 -0
- data/test/unit/model_import_test.rb +71 -0
- data/test/unit/model_persistence_test.rb +528 -0
- data/test/unit/model_search_test.rb +913 -0
- data/test/unit/results_collection_test.rb +281 -0
- data/test/unit/results_item_test.rb +162 -0
- data/test/unit/rubyext_test.rb +66 -0
- data/test/unit/search_facet_test.rb +153 -0
- data/test/unit/search_filter_test.rb +42 -0
- data/test/unit/search_highlight_test.rb +46 -0
- data/test/unit/search_query_test.rb +301 -0
- data/test/unit/search_scan_test.rb +113 -0
- data/test/unit/search_script_field_test.rb +26 -0
- data/test/unit/search_sort_test.rb +50 -0
- data/test/unit/search_test.rb +499 -0
- data/test/unit/tire_test.rb +126 -0
- data/tire.gemspec +90 -0
- metadata +549 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Tire
|
5
|
+
|
6
|
+
class LoggerTest < Test::Unit::TestCase
|
7
|
+
include Tire
|
8
|
+
|
9
|
+
context "Logger" do
|
10
|
+
|
11
|
+
context "initialized with an IO object" do
|
12
|
+
|
13
|
+
should "take STDOUT" do
|
14
|
+
assert_nothing_raised do
|
15
|
+
logger = Logger.new STDOUT
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
should "write to STDERR" do
|
20
|
+
STDERR.expects(:write).with('BOOM!')
|
21
|
+
logger = Logger.new STDERR
|
22
|
+
logger.write('BOOM!')
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
context "initialized with file" do
|
28
|
+
teardown { File.delete('myfile.log') }
|
29
|
+
|
30
|
+
should "create the file" do
|
31
|
+
assert_nothing_raised do
|
32
|
+
logger = Logger.new 'myfile.log'
|
33
|
+
assert File.exists?('myfile.log')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
should "write to file" do
|
38
|
+
File.any_instance.expects(:write).with('BOOM!')
|
39
|
+
logger = Logger.new 'myfile.log'
|
40
|
+
logger.write('BOOM!')
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
context "levels" do
|
48
|
+
|
49
|
+
should "have the default level" do
|
50
|
+
logger = Logger.new STDERR
|
51
|
+
assert_equal 'info', logger.level
|
52
|
+
end
|
53
|
+
|
54
|
+
should "set the level" do
|
55
|
+
logger = Logger.new STDERR, :level => 'debug'
|
56
|
+
assert_equal 'debug', logger.level
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
context "tracing requests" do
|
62
|
+
setup do
|
63
|
+
Time.stubs(:now).returns(Time.parse('2011-03-19 11:00'))
|
64
|
+
@logger = Logger.new STDERR
|
65
|
+
end
|
66
|
+
|
67
|
+
should "log request in correct format" do
|
68
|
+
log = (<<-"log;").gsub(/^ +/, '')
|
69
|
+
# 2011-03-19 11:00:00:000 [_search] (["articles", "users"])
|
70
|
+
#
|
71
|
+
curl -X GET http://...
|
72
|
+
|
73
|
+
log;
|
74
|
+
@logger.expects(:write).with do |payload|
|
75
|
+
payload =~ Regexp.new( Regexp.escape('2011-03-19 11:00:00') )
|
76
|
+
payload =~ Regexp.new( Regexp.escape('_search') )
|
77
|
+
payload =~ Regexp.new( Regexp.escape('(["articles", "users"])') )
|
78
|
+
end
|
79
|
+
@logger.log_request('_search', ["articles", "users"], 'curl -X GET http://...')
|
80
|
+
end
|
81
|
+
|
82
|
+
should "log response in correct format" do
|
83
|
+
json = (<<-"json;").gsub(/^\s*/, '')
|
84
|
+
{
|
85
|
+
"took" : 4,
|
86
|
+
"hits" : {
|
87
|
+
"total" : 20,
|
88
|
+
"max_score" : 1.0,
|
89
|
+
"hits" : [ {
|
90
|
+
"_index" : "articles",
|
91
|
+
"_type" : "article",
|
92
|
+
"_id" : "Hmg0B0VSRKm2VAlsasdnqg",
|
93
|
+
"_score" : 1.0, "_source" : { "title" : "Article 1", "published_on" : "2011-01-01" }
|
94
|
+
}, {
|
95
|
+
"_index" : "articles",
|
96
|
+
"_type" : "article",
|
97
|
+
"_id" : "booSWC8eRly2I06GTUilNA",
|
98
|
+
"_score" : 1.0, "_source" : { "title" : "Article 2", "published_on" : "2011-01-12" }
|
99
|
+
}
|
100
|
+
]
|
101
|
+
}
|
102
|
+
}
|
103
|
+
json;
|
104
|
+
log = (<<-"log;").gsub(/^\s*/, '')
|
105
|
+
# 2011-03-19 11:00:00:000 [200 OK] (4 msec)
|
106
|
+
#
|
107
|
+
log;
|
108
|
+
# log += json.split.map { |line| "# #{line}" }.join("\n")
|
109
|
+
json.each_line { |line| log += "# #{line}" }
|
110
|
+
log += "\n\n"
|
111
|
+
@logger.expects(:write).with do |payload|
|
112
|
+
payload =~ Regexp.new( Regexp.escape('2011-03-19 11:00:00') )
|
113
|
+
payload =~ Regexp.new( Regexp.escape('[200 OK]') )
|
114
|
+
payload =~ Regexp.new( Regexp.escape('(4 msec)') )
|
115
|
+
payload =~ Regexp.new( Regexp.escape('took') )
|
116
|
+
payload =~ Regexp.new( Regexp.escape('hits') )
|
117
|
+
payload =~ Regexp.new( Regexp.escape('_score') )
|
118
|
+
end
|
119
|
+
@logger.log_response('200 OK', 4, json)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ModelOne
|
4
|
+
extend ActiveModel::Naming
|
5
|
+
include Tire::Model::Search
|
6
|
+
include Tire::Model::Callbacks
|
7
|
+
|
8
|
+
def save; false; end
|
9
|
+
def destroy; false; end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ModelTwo
|
13
|
+
extend ActiveModel::Naming
|
14
|
+
extend ActiveModel::Callbacks
|
15
|
+
define_model_callbacks :save, :destroy
|
16
|
+
|
17
|
+
include Tire::Model::Search
|
18
|
+
include Tire::Model::Callbacks
|
19
|
+
|
20
|
+
def save; _run_save_callbacks {}; end
|
21
|
+
def destroy; _run_destroy_callbacks { @destroyed = true }; end
|
22
|
+
|
23
|
+
def destroyed?; !!@destroyed; end
|
24
|
+
end
|
25
|
+
|
26
|
+
class ModelThree
|
27
|
+
extend ActiveModel::Naming
|
28
|
+
extend ActiveModel::Callbacks
|
29
|
+
define_model_callbacks :save, :destroy
|
30
|
+
|
31
|
+
include Tire::Model::Search
|
32
|
+
include Tire::Model::Callbacks
|
33
|
+
|
34
|
+
def save; _run_save_callbacks {}; end
|
35
|
+
def destroy; _run_destroy_callbacks {}; end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ModelWithoutTireAutoCallbacks
|
39
|
+
extend ActiveModel::Naming
|
40
|
+
extend ActiveModel::Callbacks
|
41
|
+
define_model_callbacks :save, :destroy
|
42
|
+
|
43
|
+
include Tire::Model::Search
|
44
|
+
# DO NOT include Callbacks
|
45
|
+
|
46
|
+
def save; _run_save_callbacks {}; end
|
47
|
+
def destroy; _run_destroy_callbacks {}; end
|
48
|
+
end
|
49
|
+
|
50
|
+
module Tire
|
51
|
+
module Model
|
52
|
+
|
53
|
+
class ModelCallbacksTest < Test::Unit::TestCase
|
54
|
+
|
55
|
+
context "Model without ActiveModel callbacks" do
|
56
|
+
|
57
|
+
should "not execute any callbacks" do
|
58
|
+
m = ModelOne.new
|
59
|
+
m.tire.expects(:update_index).never
|
60
|
+
|
61
|
+
m.save
|
62
|
+
m.destroy
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
context "Model with ActiveModel callbacks and implemented destroyed? method" do
|
68
|
+
|
69
|
+
should "execute the callbacks" do
|
70
|
+
m = ModelTwo.new
|
71
|
+
m.tire.expects(:update_index).twice
|
72
|
+
|
73
|
+
m.save
|
74
|
+
m.destroy
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
context "Model with ActiveModel callbacks without destroyed? method implemented" do
|
80
|
+
|
81
|
+
should "have the destroyed? method added" do
|
82
|
+
assert_respond_to ModelThree.new, :destroyed?
|
83
|
+
end
|
84
|
+
|
85
|
+
should "execute the callbacks" do
|
86
|
+
m = ModelThree.new
|
87
|
+
m.tire.expects(:update_index).twice
|
88
|
+
|
89
|
+
m.save
|
90
|
+
m.destroy
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
context "Model without Tire::Callbacks included" do
|
96
|
+
|
97
|
+
should "respond to Tire update_index callbacks" do
|
98
|
+
assert_respond_to ModelWithoutTireAutoCallbacks, :after_update_elasticsearch_index
|
99
|
+
assert_respond_to ModelWithoutTireAutoCallbacks, :before_update_elasticsearch_index
|
100
|
+
end
|
101
|
+
|
102
|
+
should "not execute the update_index hooks" do
|
103
|
+
m = ModelWithoutTireAutoCallbacks.new
|
104
|
+
m.tire.expects(:update_index).never
|
105
|
+
|
106
|
+
m.save
|
107
|
+
m.destroy
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# ---------------------------------------------------------------------------
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ImportModel
|
4
|
+
extend ActiveModel::Naming
|
5
|
+
include Tire::Model::Search
|
6
|
+
include Tire::Model::Callbacks
|
7
|
+
|
8
|
+
DATA = (1..4).to_a
|
9
|
+
|
10
|
+
def self.paginate(options={})
|
11
|
+
options = {:page => 1, :per_page => 1000}.update options
|
12
|
+
DATA.slice( (options[:page]-1)*options[:per_page]...options[:page]*options[:per_page] )
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.all(options={})
|
16
|
+
DATA
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.count
|
20
|
+
DATA.size
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Tire
|
25
|
+
module Model
|
26
|
+
|
27
|
+
class ImportTest < Test::Unit::TestCase
|
28
|
+
|
29
|
+
context "Model::Import" do
|
30
|
+
|
31
|
+
should "have the import method" do
|
32
|
+
assert_respond_to ImportModel, :import
|
33
|
+
end
|
34
|
+
|
35
|
+
should "paginate the results by default when importing" do
|
36
|
+
Tire::Index.any_instance.expects(:bulk_store).returns(true).times(2)
|
37
|
+
|
38
|
+
ImportModel.import :per_page => 2
|
39
|
+
end
|
40
|
+
|
41
|
+
should "call the passed block on every batch, and NOT manipulate the documents array" do
|
42
|
+
Tire::Index.any_instance.expects(:bulk_store).with { |c,o| c == [1, 2] }
|
43
|
+
Tire::Index.any_instance.expects(:bulk_store).with { |c,o| c == [3, 4] }
|
44
|
+
|
45
|
+
runs = 0
|
46
|
+
ImportModel.import :per_page => 2 do |documents|
|
47
|
+
runs += 1
|
48
|
+
# Don't forget to return the documents at the end of the block
|
49
|
+
documents
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_equal 2, runs
|
53
|
+
end
|
54
|
+
|
55
|
+
should "manipulate the documents in passed block" do
|
56
|
+
Tire::Index.any_instance.expects(:bulk_store).with { |c,o| c == [2, 3] }
|
57
|
+
Tire::Index.any_instance.expects(:bulk_store).with { |c,o| c == [4, 5] }
|
58
|
+
|
59
|
+
ImportModel.import :per_page => 2 do |documents|
|
60
|
+
# Add 1 to every "document" and return them
|
61
|
+
documents.map { |d| d + 1 }
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,528 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
module Model
|
5
|
+
|
6
|
+
class PersistenceTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
context "Model" do
|
9
|
+
|
10
|
+
should "have default index name" do
|
11
|
+
assert_equal 'persistent_articles', PersistentArticle.index_name
|
12
|
+
assert_equal 'persistent_articles', PersistentArticle.new(:title => 'Test').index_name
|
13
|
+
end
|
14
|
+
|
15
|
+
should "allow to set custom index name" do
|
16
|
+
assert_equal 'custom-index-name', PersistentArticleWithCustomIndexName.index_name
|
17
|
+
|
18
|
+
PersistentArticleWithCustomIndexName.index_name "another-index-name"
|
19
|
+
assert_equal 'another-index-name', PersistentArticleWithCustomIndexName.index_name
|
20
|
+
assert_equal 'another-index-name', PersistentArticleWithCustomIndexName.index.name
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with index prefix" do
|
24
|
+
setup do
|
25
|
+
Model::Search.index_prefix 'prefix'
|
26
|
+
end
|
27
|
+
|
28
|
+
teardown do
|
29
|
+
Model::Search.index_prefix nil
|
30
|
+
end
|
31
|
+
|
32
|
+
should "have configured prefix in index_name" do
|
33
|
+
assert_equal 'prefix_persistent_articles', PersistentArticle.index_name
|
34
|
+
assert_equal 'prefix_persistent_articles', PersistentArticle.new(:title => 'Test').index_name
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
should "have document_type" do
|
40
|
+
assert_equal 'persistent_article', PersistentArticle.document_type
|
41
|
+
assert_equal 'persistent_article', PersistentArticle.new(:title => 'Test').document_type
|
42
|
+
end
|
43
|
+
|
44
|
+
should "allow to define property" do
|
45
|
+
assert_nothing_raised do
|
46
|
+
a = PersistentArticle.new
|
47
|
+
class << a
|
48
|
+
property :status
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
context "Finders" do
|
56
|
+
|
57
|
+
setup do
|
58
|
+
@first = { '_id' => 1, '_version' => 1, '_index' => 'persistent_articles', '_type' => 'persistent_article', '_source' => { :title => 'First' } }
|
59
|
+
@second = { '_id' => 2, '_index' => 'persistent_articles', '_type' => 'persistent_article', '_source' => { :title => 'Second' } }
|
60
|
+
@third = { '_id' => 3, '_index' => 'persistent_articles', '_type' => 'persistent_article', '_source' => { :title => 'Third' } }
|
61
|
+
@find_all = { 'hits' => { 'hits' => [
|
62
|
+
@first,
|
63
|
+
@second,
|
64
|
+
@third
|
65
|
+
] } }
|
66
|
+
@find_first = { 'hits' => { 'hits' => [ @first ] } }
|
67
|
+
@find_last_two = { 'hits' => { 'hits' => [ @second, @third ] } }
|
68
|
+
@find_twenty_ids = { 'hits' => { 'hits' => 20.times.map { @first } } }
|
69
|
+
end
|
70
|
+
|
71
|
+
should "find document by numeric ID" do
|
72
|
+
Configuration.client.expects(:get).returns(mock_response(@first.to_json))
|
73
|
+
document = PersistentArticle.find 1
|
74
|
+
|
75
|
+
assert_instance_of PersistentArticle, document
|
76
|
+
assert_equal 1, document.id
|
77
|
+
assert_equal 1, document.attributes['id']
|
78
|
+
assert_equal 'First', document.attributes['title']
|
79
|
+
assert_equal 'First', document.title
|
80
|
+
end
|
81
|
+
|
82
|
+
should "have _type, _index, _id, _version attributes" do
|
83
|
+
Configuration.client.expects(:get).returns(mock_response(@first.to_json))
|
84
|
+
document = PersistentArticle.find 1
|
85
|
+
|
86
|
+
assert_instance_of PersistentArticle, document
|
87
|
+
assert_equal 1, document.id
|
88
|
+
assert_equal 1, document.attributes['id']
|
89
|
+
assert_equal 'persistent_articles', document._index
|
90
|
+
assert_equal 'persistent_article', document._type
|
91
|
+
assert_equal 1, document._version
|
92
|
+
end
|
93
|
+
|
94
|
+
should "find document by string ID" do
|
95
|
+
Configuration.client.expects(:get).returns(mock_response(@first.to_json))
|
96
|
+
document = PersistentArticle.find '1'
|
97
|
+
|
98
|
+
assert_instance_of PersistentArticle, document
|
99
|
+
assert_equal 1, document.id
|
100
|
+
assert_equal 1, document.attributes['id']
|
101
|
+
assert_equal 'First', document.attributes['title']
|
102
|
+
assert_equal 'First', document.title
|
103
|
+
end
|
104
|
+
|
105
|
+
should "find document by list of IDs" do
|
106
|
+
Configuration.client.expects(:get).returns(mock_response(@find_last_two.to_json))
|
107
|
+
documents = PersistentArticle.find 2, 3
|
108
|
+
|
109
|
+
assert_equal 2, documents.count
|
110
|
+
end
|
111
|
+
|
112
|
+
should "find document by array of IDs" do
|
113
|
+
Configuration.client.expects(:get).returns(mock_response(@find_last_two.to_json))
|
114
|
+
documents = PersistentArticle.find [2, 3]
|
115
|
+
|
116
|
+
assert_equal 2, documents.count
|
117
|
+
end
|
118
|
+
|
119
|
+
should "find all documents listed in IDs array" do
|
120
|
+
ids = (1..20).to_a
|
121
|
+
Configuration.client.expects(:get).returns(mock_response(@find_twenty_ids.to_json))
|
122
|
+
Tire::Search::Search.any_instance.expects(:size).with(ids.size)
|
123
|
+
|
124
|
+
documents = PersistentArticle.find ids
|
125
|
+
assert_equal ids.size, documents.count
|
126
|
+
end
|
127
|
+
|
128
|
+
should "find all documents" do
|
129
|
+
Configuration.client.stubs(:get).returns(mock_response(@find_all.to_json))
|
130
|
+
documents = PersistentArticle.all
|
131
|
+
|
132
|
+
assert_equal 3, documents.count
|
133
|
+
assert_equal 'First', documents.first.attributes['title']
|
134
|
+
assert_equal PersistentArticle.find(:all).map { |e| e.id }, PersistentArticle.all.map { |e| e.id }
|
135
|
+
end
|
136
|
+
|
137
|
+
should "find first document" do
|
138
|
+
Configuration.client.expects(:get).returns(mock_response(@find_first.to_json))
|
139
|
+
document = PersistentArticle.first
|
140
|
+
|
141
|
+
assert_equal 'First', document.attributes['title']
|
142
|
+
end
|
143
|
+
|
144
|
+
should "raise error when passing incorrect argument" do
|
145
|
+
assert_raise(ArgumentError) do
|
146
|
+
PersistentArticle.find :name => 'Test'
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
should_eventually "raise error when document is not found" do
|
151
|
+
assert_raise(DocumentNotFound) do
|
152
|
+
PersistentArticle.find 'xyz001'
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
context "Persistent model" do
|
159
|
+
|
160
|
+
setup { @article = PersistentArticle.new :title => 'Test', :tags => [:one, :two] }
|
161
|
+
|
162
|
+
context "attribute methods" do
|
163
|
+
|
164
|
+
should "allow to set attributes on initialization" do
|
165
|
+
assert_not_nil @article.attributes
|
166
|
+
assert_equal 'Test', @article.attributes['title']
|
167
|
+
end
|
168
|
+
|
169
|
+
should "allow to leave attributes blank on initialization" do
|
170
|
+
assert_nothing_raised { PersistentArticle.new }
|
171
|
+
end
|
172
|
+
|
173
|
+
should "have getter methods for attributes" do
|
174
|
+
assert_not_nil @article.title
|
175
|
+
assert_equal 'Test', @article.title
|
176
|
+
assert_equal [:one, :two], @article.tags
|
177
|
+
end
|
178
|
+
|
179
|
+
should "have getter methods for attribute passed as a String" do
|
180
|
+
article = PersistentArticle.new 'title' => 'Tony Montana'
|
181
|
+
assert_not_nil article.title
|
182
|
+
assert_equal 'Tony Montana', article.title
|
183
|
+
end
|
184
|
+
|
185
|
+
should "raise error when getting unknown attribute" do
|
186
|
+
assert_raise(NoMethodError) do
|
187
|
+
@article.krapulitz
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
should "not raise error when getting unset attribute" do
|
192
|
+
article = PersistentArticle.new :title => 'Test'
|
193
|
+
|
194
|
+
assert_nothing_raised { article.published_on }
|
195
|
+
assert_nil article.published_on
|
196
|
+
end
|
197
|
+
|
198
|
+
should "return default value for attribute" do
|
199
|
+
article = PersistentArticleWithDefaults.new :title => 'Test'
|
200
|
+
assert_equal [], article.tags
|
201
|
+
assert_equal false, article.hidden
|
202
|
+
end
|
203
|
+
|
204
|
+
should "not affect default value" do
|
205
|
+
article = PersistentArticleWithDefaults.new :title => 'Test'
|
206
|
+
article.tags << "ruby"
|
207
|
+
|
208
|
+
article.options[:switches] << "switch_1"
|
209
|
+
|
210
|
+
assert_equal [], PersistentArticleWithDefaults.new.tags
|
211
|
+
assert_equal [], PersistentArticleWithDefaults.new.options[:switches]
|
212
|
+
end
|
213
|
+
|
214
|
+
should "have query method for attribute" do
|
215
|
+
assert_equal true, @article.title?
|
216
|
+
end
|
217
|
+
|
218
|
+
should "raise error when querying for unknown attribute" do
|
219
|
+
assert_raise(NoMethodError) do
|
220
|
+
@article.krapulitz?
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
should "not raise error when querying for unset attribute" do
|
225
|
+
article = PersistentArticle.new :title => 'Test'
|
226
|
+
|
227
|
+
assert_nothing_raised { article.published_on? }
|
228
|
+
assert ! article.published_on?
|
229
|
+
end
|
230
|
+
|
231
|
+
should "return true for respond_to? calls for set attributes" do
|
232
|
+
article = PersistentArticle.new :title => 'Test'
|
233
|
+
assert article.respond_to?(:title)
|
234
|
+
end
|
235
|
+
|
236
|
+
should "return false for respond_to? calls for unknown attributes" do
|
237
|
+
article = PersistentArticle.new :title => 'Test'
|
238
|
+
assert ! article.respond_to?(:krapulitz)
|
239
|
+
end
|
240
|
+
|
241
|
+
should "return true for respond_to? calls for defined but unset attributes" do
|
242
|
+
article = PersistentArticle.new :title => 'Test'
|
243
|
+
|
244
|
+
assert article.respond_to?(:published_on)
|
245
|
+
end
|
246
|
+
|
247
|
+
should "have attribute names" do
|
248
|
+
article = PersistentArticle.new :title => 'Test', :tags => ['one', 'two']
|
249
|
+
assert_equal ['published_on', 'tags', 'title'], article.attribute_names
|
250
|
+
end
|
251
|
+
|
252
|
+
should "have setter method for attribute" do
|
253
|
+
@article.title = 'Updated'
|
254
|
+
assert_equal 'Updated', @article.title
|
255
|
+
assert_equal 'Updated', @article.attributes['title']
|
256
|
+
end
|
257
|
+
|
258
|
+
should_eventually "allow to set deeply nested attributes on initialization" do
|
259
|
+
article = PersistentArticle.new :title => 'Test', :author => { :first_name => 'John', :last_name => 'Smith' }
|
260
|
+
|
261
|
+
assert_equal 'John', article.author.first_name
|
262
|
+
assert_equal 'Smith', article.author.last_name
|
263
|
+
assert_equal({ :first_name => 'John', :last_name => 'Smith' }, article.attributes['author'])
|
264
|
+
end
|
265
|
+
|
266
|
+
should_eventually "allow to set deeply nested attributes on update" do
|
267
|
+
article = PersistentArticle.new :title => 'Test', :author => { :first_name => 'John', :last_name => 'Smith' }
|
268
|
+
|
269
|
+
article.author.first_name = 'Robert'
|
270
|
+
article.author.last_name = 'Carpenter'
|
271
|
+
|
272
|
+
assert_equal 'Robert', article.author.first_name
|
273
|
+
assert_equal 'Carpenter', article.author.last_name
|
274
|
+
assert_equal({ :first_name => 'Robert', :last_name => 'Carpenter' }, article.attributes['author'])
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
context "with casting" do
|
280
|
+
|
281
|
+
should "cast the value as custom class" do
|
282
|
+
article = PersistentArticleWithCastedItem.new :title => 'Test',
|
283
|
+
:author => { :first_name => 'John', :last_name => 'Smith' }
|
284
|
+
assert_instance_of Author, article.author
|
285
|
+
assert_equal 'John', article.author.first_name
|
286
|
+
end
|
287
|
+
|
288
|
+
should "cast the value as collection of custom classes" do
|
289
|
+
article = PersistentArticleWithCastedCollection.new :title => 'Test',
|
290
|
+
:comments => [{:nick => '4chan', :body => 'WHY U NO?'}]
|
291
|
+
assert_instance_of Array, article.comments
|
292
|
+
assert_instance_of Comment, article.comments.first
|
293
|
+
assert_equal '4chan', article.comments.first.nick
|
294
|
+
end
|
295
|
+
|
296
|
+
should "automatically format strings in UTC format as Time" do
|
297
|
+
article = PersistentArticle.new :published_on => '2011-11-01T23:00:00Z'
|
298
|
+
assert_instance_of Time, article.published_on
|
299
|
+
assert_equal 2011, article.published_on.year
|
300
|
+
end
|
301
|
+
|
302
|
+
should "cast anonymous Hashes as Hashr instances" do
|
303
|
+
article = PersistentArticleWithCastedItem.new :stats => { :views => 100, :meta => { :tags => 'A' } }
|
304
|
+
assert_equal 100, article.stats.views
|
305
|
+
assert_equal 'A', article.stats.meta.tags
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
|
310
|
+
context "when initializing" do
|
311
|
+
|
312
|
+
should "be a new record" do
|
313
|
+
article = PersistentArticle.new :title => 'Test'
|
314
|
+
|
315
|
+
assert article.new_record?, "#{article.inspect} should be `new_record?`"
|
316
|
+
assert ! article.persisted?, "#{article.inspect} should NOT be `persisted?`"
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
|
321
|
+
context "when creating" do
|
322
|
+
|
323
|
+
should "save the document with generated ID in the database" do
|
324
|
+
Configuration.client.expects(:post).
|
325
|
+
with do |url, payload|
|
326
|
+
doc = MultiJson.decode(payload)
|
327
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/" &&
|
328
|
+
doc['title'] == 'Test' &&
|
329
|
+
doc['tags'] == ['one', 'two']
|
330
|
+
doc['published_on'] == nil
|
331
|
+
end.
|
332
|
+
returns(mock_response('{"ok":true,"_id":"abc123","_version":1}'))
|
333
|
+
article = PersistentArticle.create :title => 'Test', :tags => [:one, :two]
|
334
|
+
|
335
|
+
assert article.persisted?, "#{article.inspect} should be `persisted?`"
|
336
|
+
assert ! article.new_record?, "#{article.inspect} should NOT be `new_record?`"
|
337
|
+
assert_equal 'abc123', article.id
|
338
|
+
end
|
339
|
+
|
340
|
+
should "save the document with custom ID in the database" do
|
341
|
+
Configuration.client.expects(:post).
|
342
|
+
with do |url, payload|
|
343
|
+
doc = MultiJson.decode(payload)
|
344
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/r2d2" &&
|
345
|
+
doc['title'] == 'Test' &&
|
346
|
+
doc['published_on'] == nil
|
347
|
+
end.
|
348
|
+
returns(mock_response('{"ok":true,"_id":"r2d2"}'))
|
349
|
+
article = PersistentArticle.create :id => 'r2d2', :title => 'Test'
|
350
|
+
|
351
|
+
assert_equal 'r2d2', article.id
|
352
|
+
end
|
353
|
+
|
354
|
+
should "perform model validations" do
|
355
|
+
Configuration.client.expects(:post).never
|
356
|
+
|
357
|
+
assert ! ValidatedModel.create(:name => nil)
|
358
|
+
end
|
359
|
+
|
360
|
+
end
|
361
|
+
|
362
|
+
context "when creating" do
|
363
|
+
|
364
|
+
should "set the id property" do
|
365
|
+
Configuration.client.expects(:post).
|
366
|
+
with do |url, payload|
|
367
|
+
doc = MultiJson.decode(payload)
|
368
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/" &&
|
369
|
+
doc['title'] == 'Test'
|
370
|
+
end.
|
371
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
372
|
+
|
373
|
+
article = PersistentArticle.create :title => 'Test'
|
374
|
+
assert_equal '1', article.id
|
375
|
+
end
|
376
|
+
|
377
|
+
should "not set the id property if already set" do
|
378
|
+
Configuration.client.expects(:post).
|
379
|
+
with do |url, payload|
|
380
|
+
doc = MultiJson.decode(payload)
|
381
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/123" &&
|
382
|
+
doc['title'] == 'Test' &&
|
383
|
+
doc['published_on'] == nil
|
384
|
+
end.
|
385
|
+
returns(mock_response('{"ok":true, "_id":"XXX"}'))
|
386
|
+
|
387
|
+
article = PersistentArticle.create :id => '123', :title => 'Test'
|
388
|
+
assert_equal '123', article.id
|
389
|
+
end
|
390
|
+
|
391
|
+
end
|
392
|
+
|
393
|
+
context "when saving" do
|
394
|
+
|
395
|
+
should "save the document with updated attribute" do
|
396
|
+
article = PersistentArticle.new :id => '1', :title => 'Test'
|
397
|
+
|
398
|
+
Configuration.client.expects(:post).
|
399
|
+
with do |url, payload|
|
400
|
+
doc = MultiJson.decode(payload)
|
401
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/1" &&
|
402
|
+
doc['title'] == 'Test' &&
|
403
|
+
doc['published_on'] == nil
|
404
|
+
end.
|
405
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
406
|
+
assert article.save
|
407
|
+
|
408
|
+
article.title = 'Updated'
|
409
|
+
|
410
|
+
Configuration.client.expects(:post).
|
411
|
+
with do |url, payload|
|
412
|
+
doc = MultiJson.decode(payload)
|
413
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/1" &&
|
414
|
+
doc['title'] == 'Updated'
|
415
|
+
end.
|
416
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
417
|
+
assert article.save
|
418
|
+
end
|
419
|
+
|
420
|
+
should "perform validations" do
|
421
|
+
article = ValidatedModel.new :name => nil
|
422
|
+
assert ! article.save
|
423
|
+
end
|
424
|
+
|
425
|
+
should "set the id property itself" do
|
426
|
+
article = PersistentArticle.new
|
427
|
+
article.title = 'Test'
|
428
|
+
|
429
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/persistent_articles/persistent_article/",
|
430
|
+
article.to_indexed_json).
|
431
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
432
|
+
assert article.save
|
433
|
+
assert_equal '1', article.id
|
434
|
+
end
|
435
|
+
|
436
|
+
should "not set the id property if already set" do
|
437
|
+
article = PersistentArticle.new
|
438
|
+
article.id = '456'
|
439
|
+
article.title = 'Test'
|
440
|
+
|
441
|
+
Configuration.client.expects(:post).
|
442
|
+
with do |url, payload|
|
443
|
+
doc = MultiJson.decode(payload)
|
444
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/456" &&
|
445
|
+
doc['title'] == 'Test'
|
446
|
+
end.
|
447
|
+
returns(mock_response('{"ok":true,"_id":"XXX"}'))
|
448
|
+
assert article.save
|
449
|
+
assert_equal '456', article.id
|
450
|
+
end
|
451
|
+
|
452
|
+
end
|
453
|
+
|
454
|
+
context "when destroying" do
|
455
|
+
|
456
|
+
should "delete the document from the database" do
|
457
|
+
Configuration.client.expects(:post).
|
458
|
+
with do |url, payload|
|
459
|
+
doc = MultiJson.decode(payload)
|
460
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/123" &&
|
461
|
+
doc['title'] == 'Test'
|
462
|
+
end.returns(mock_response('{"ok":true,"_id":"123"}'))
|
463
|
+
|
464
|
+
Configuration.client.expects(:delete).
|
465
|
+
with("#{Configuration.url}/persistent_articles/persistent_article/123").
|
466
|
+
returns(mock_response('{"ok":true,"acknowledged":true}', 200))
|
467
|
+
|
468
|
+
article = PersistentArticle.new :id => '123', :title => 'Test'
|
469
|
+
article.save
|
470
|
+
article.destroy
|
471
|
+
end
|
472
|
+
|
473
|
+
end
|
474
|
+
|
475
|
+
context "when updating attributes" do
|
476
|
+
|
477
|
+
should "update single attribute" do
|
478
|
+
@article.expects(:save).returns(true)
|
479
|
+
|
480
|
+
@article.update_attribute :title, 'Updated'
|
481
|
+
assert_equal 'Updated', @article.title
|
482
|
+
end
|
483
|
+
|
484
|
+
should "update all attributes" do
|
485
|
+
@article.expects(:save).returns(true)
|
486
|
+
|
487
|
+
@article.update_attributes :title => 'Updated', :tags => ['three']
|
488
|
+
assert_equal 'Updated', @article.title
|
489
|
+
assert_equal ['three'], @article.tags
|
490
|
+
end
|
491
|
+
|
492
|
+
end
|
493
|
+
|
494
|
+
end
|
495
|
+
|
496
|
+
context "Persistent model with mapping definition" do
|
497
|
+
|
498
|
+
should "create the index with mapping" do
|
499
|
+
expected = {
|
500
|
+
:settings => {},
|
501
|
+
:mappings => { :persistent_article_with_mapping => {
|
502
|
+
:properties => { :title => { :type => 'string', :analyzer => 'snowball', :boost => 10 } }
|
503
|
+
}}
|
504
|
+
}
|
505
|
+
|
506
|
+
Tire::Index.any_instance.stubs(:exists?).returns(false)
|
507
|
+
Tire::Index.any_instance.expects(:create).with(expected)
|
508
|
+
|
509
|
+
class ::PersistentArticleWithMapping
|
510
|
+
|
511
|
+
include Tire::Model::Persistence
|
512
|
+
include Tire::Model::Search
|
513
|
+
include Tire::Model::Callbacks
|
514
|
+
|
515
|
+
mapping do
|
516
|
+
property :title, :type => 'string', :analyzer => 'snowball', :boost => 10
|
517
|
+
end
|
518
|
+
|
519
|
+
end
|
520
|
+
|
521
|
+
assert_equal 'snowball', PersistentArticleWithMapping.mapping[:title][:analyzer]
|
522
|
+
end
|
523
|
+
|
524
|
+
end
|
525
|
+
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|