tire 0.1.0
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 +9 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +435 -0
- data/Rakefile +75 -0
- data/examples/dsl.rb +73 -0
- data/examples/rails-application-template.rb +144 -0
- data/examples/tire-dsl.rb +617 -0
- data/lib/tire.rb +35 -0
- data/lib/tire/client.rb +40 -0
- data/lib/tire/configuration.rb +29 -0
- data/lib/tire/dsl.rb +33 -0
- data/lib/tire/index.rb +209 -0
- data/lib/tire/logger.rb +60 -0
- data/lib/tire/model/callbacks.rb +23 -0
- data/lib/tire/model/import.rb +18 -0
- data/lib/tire/model/indexing.rb +50 -0
- data/lib/tire/model/naming.rb +30 -0
- data/lib/tire/model/persistence.rb +34 -0
- data/lib/tire/model/persistence/attributes.rb +60 -0
- data/lib/tire/model/persistence/finders.rb +61 -0
- data/lib/tire/model/persistence/storage.rb +75 -0
- data/lib/tire/model/search.rb +97 -0
- data/lib/tire/results/collection.rb +56 -0
- data/lib/tire/results/item.rb +39 -0
- data/lib/tire/results/pagination.rb +30 -0
- data/lib/tire/rubyext/hash.rb +3 -0
- data/lib/tire/rubyext/symbol.rb +11 -0
- data/lib/tire/search.rb +117 -0
- data/lib/tire/search/facet.rb +41 -0
- data/lib/tire/search/filter.rb +28 -0
- data/lib/tire/search/highlight.rb +37 -0
- data/lib/tire/search/query.rb +42 -0
- data/lib/tire/search/sort.rb +29 -0
- data/lib/tire/tasks.rb +88 -0
- data/lib/tire/version.rb +3 -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_searchable_test.rb +80 -0
- data/test/integration/active_record_searchable_test.rb +193 -0
- data/test/integration/facets_test.rb +65 -0
- data/test/integration/filters_test.rb +46 -0
- data/test/integration/highlight_test.rb +52 -0
- data/test/integration/index_mapping_test.rb +44 -0
- data/test/integration/index_store_test.rb +68 -0
- data/test/integration/persistent_model_test.rb +35 -0
- data/test/integration/query_string_test.rb +43 -0
- data/test/integration/results_test.rb +28 -0
- data/test/integration/sort_test.rb +36 -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_index_name.rb +5 -0
- data/test/models/active_record_article.rb +12 -0
- data/test/models/article.rb +15 -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 +52 -0
- data/test/unit/active_model_lint_test.rb +17 -0
- data/test/unit/client_test.rb +43 -0
- data/test/unit/configuration_test.rb +71 -0
- data/test/unit/index_test.rb +390 -0
- data/test/unit/logger_test.rb +114 -0
- 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 +131 -0
- data/test/unit/results_item_test.rb +59 -0
- data/test/unit/rubyext_hash_test.rb +19 -0
- data/test/unit/search_facet_test.rb +69 -0
- data/test/unit/search_filter_test.rb +36 -0
- data/test/unit/search_highlight_test.rb +46 -0
- data/test/unit/search_query_test.rb +55 -0
- data/test/unit/search_sort_test.rb +50 -0
- data/test/unit/search_test.rb +204 -0
- data/test/unit/tire_test.rb +55 -0
- data/tire.gemspec +54 -0
- metadata +372 -0
@@ -0,0 +1,114 @@
|
|
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:L [_search] (["articles", "users"])
|
70
|
+
#
|
71
|
+
curl -X GET http://...
|
72
|
+
|
73
|
+
log;
|
74
|
+
@logger.expects(:write).with(log)
|
75
|
+
@logger.log_request('_search', ["articles", "users"], 'curl -X GET http://...')
|
76
|
+
end
|
77
|
+
|
78
|
+
should "log response in correct format" do
|
79
|
+
json = (<<-"json;").gsub(/^\s*/, '')
|
80
|
+
{
|
81
|
+
"took" : 4,
|
82
|
+
"hits" : {
|
83
|
+
"total" : 20,
|
84
|
+
"max_score" : 1.0,
|
85
|
+
"hits" : [ {
|
86
|
+
"_index" : "articles",
|
87
|
+
"_type" : "article",
|
88
|
+
"_id" : "Hmg0B0VSRKm2VAlsasdnqg",
|
89
|
+
"_score" : 1.0, "_source" : { "title" : "Article 1", "published_on" : "2011-01-01" }
|
90
|
+
}, {
|
91
|
+
"_index" : "articles",
|
92
|
+
"_type" : "article",
|
93
|
+
"_id" : "booSWC8eRly2I06GTUilNA",
|
94
|
+
"_score" : 1.0, "_source" : { "title" : "Article 2", "published_on" : "2011-01-12" }
|
95
|
+
}
|
96
|
+
]
|
97
|
+
}
|
98
|
+
}
|
99
|
+
json;
|
100
|
+
log = (<<-"log;").gsub(/^\s*/, '')
|
101
|
+
# 2011-03-19 11:00:00:L [200 OK] (4 msec)
|
102
|
+
#
|
103
|
+
log;
|
104
|
+
# log += json.split.map { |line| "# #{line}" }.join("\n")
|
105
|
+
json.each_line { |line| log += "# #{line}" }
|
106
|
+
log += "\n\n"
|
107
|
+
@logger.expects(:write).with(log)
|
108
|
+
@logger.log_response('200 OK', 4, json)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ModelOne
|
4
|
+
include Tire::Model::Search
|
5
|
+
include Tire::Model::Callbacks
|
6
|
+
|
7
|
+
def save; false; end
|
8
|
+
def destroy; false; end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ModelTwo
|
12
|
+
extend ActiveModel::Callbacks
|
13
|
+
define_model_callbacks :save, :destroy
|
14
|
+
|
15
|
+
include Tire::Model::Search
|
16
|
+
include Tire::Model::Callbacks
|
17
|
+
|
18
|
+
def save
|
19
|
+
_run_save_callbacks {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy
|
23
|
+
_run_destroy_callbacks { @destroyed = true }
|
24
|
+
end
|
25
|
+
|
26
|
+
def destroyed?; !!@destroyed; end
|
27
|
+
end
|
28
|
+
|
29
|
+
class ModelThree
|
30
|
+
extend ActiveModel::Callbacks
|
31
|
+
define_model_callbacks :save, :destroy
|
32
|
+
|
33
|
+
include Tire::Model::Search
|
34
|
+
include Tire::Model::Callbacks
|
35
|
+
|
36
|
+
def save
|
37
|
+
_run_save_callbacks {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def destroy
|
41
|
+
_run_destroy_callbacks {}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module Tire
|
46
|
+
module Model
|
47
|
+
|
48
|
+
class ModelCallbacksTest < Test::Unit::TestCase
|
49
|
+
|
50
|
+
context "Model without ActiveModel callbacks" do
|
51
|
+
|
52
|
+
should "not execute any callbacks" do
|
53
|
+
ModelOne.any_instance.expects(:update_elastic_search_index).never
|
54
|
+
|
55
|
+
ModelOne.new.save
|
56
|
+
ModelOne.new.destroy
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
context "Model with ActiveModel callbacks and implemented destroyed? method" do
|
62
|
+
|
63
|
+
should "execute the callbacks" do
|
64
|
+
ModelTwo.any_instance.expects(:update_elastic_search_index).twice
|
65
|
+
|
66
|
+
ModelTwo.new.save
|
67
|
+
ModelTwo.new.destroy
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
context "Model with ActiveModel callbacks without destroyed? method implemented" do
|
73
|
+
|
74
|
+
should "have the destroyed? method added" do
|
75
|
+
assert_respond_to ModelThree.new, :destroyed?
|
76
|
+
end
|
77
|
+
|
78
|
+
should "execute the callbacks" do
|
79
|
+
ModelThree.any_instance.expects(:update_elastic_search_index).twice
|
80
|
+
|
81
|
+
ModelThree.new.save
|
82
|
+
ModelThree.new.destroy
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
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([1, 2])
|
43
|
+
Tire::Index.any_instance.expects(:bulk_store).with([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([2, 3])
|
57
|
+
Tire::Index.any_instance.expects(:bulk_store).with([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,400 @@
|
|
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
|
+
should "have document_type" do
|
24
|
+
assert_equal 'persistent_article', PersistentArticle.document_type
|
25
|
+
assert_equal 'persistent_article', PersistentArticle.new(:title => 'Test').document_type
|
26
|
+
end
|
27
|
+
|
28
|
+
should "allow to define property" do
|
29
|
+
assert_nothing_raised do
|
30
|
+
a = PersistentArticle.new
|
31
|
+
class << a
|
32
|
+
property :status
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
context "Finders" do
|
40
|
+
|
41
|
+
setup do
|
42
|
+
@first = { '_id' => 1, '_source' => { :title => 'First' } }
|
43
|
+
@second = { '_id' => 2, '_source' => { :title => 'Second' } }
|
44
|
+
@third = { '_id' => 3, '_source' => { :title => 'Third' } }
|
45
|
+
@find_all = { 'hits' => { 'hits' => [
|
46
|
+
@first,
|
47
|
+
@second,
|
48
|
+
@third
|
49
|
+
] } }
|
50
|
+
@find_first = { 'hits' => { 'hits' => [ @first ] } }
|
51
|
+
@find_last_two = { 'hits' => { 'hits' => [ @second, @third ] } }
|
52
|
+
end
|
53
|
+
|
54
|
+
should "find document by numeric ID" do
|
55
|
+
Configuration.client.expects(:get).returns(mock_response(@first.to_json))
|
56
|
+
document = PersistentArticle.find 1
|
57
|
+
|
58
|
+
assert_instance_of PersistentArticle, document
|
59
|
+
assert_equal 1, document.id
|
60
|
+
assert_equal 1, document.attributes['id']
|
61
|
+
assert_equal 'First', document.attributes['title']
|
62
|
+
assert_equal 'First', document.title
|
63
|
+
end
|
64
|
+
|
65
|
+
should "find document by string ID" do
|
66
|
+
Configuration.client.expects(:get).returns(mock_response(@first.to_json))
|
67
|
+
document = PersistentArticle.find '1'
|
68
|
+
|
69
|
+
assert_instance_of PersistentArticle, document
|
70
|
+
assert_equal 1, document.id
|
71
|
+
assert_equal 1, document.attributes['id']
|
72
|
+
assert_equal 'First', document.attributes['title']
|
73
|
+
assert_equal 'First', document.title
|
74
|
+
end
|
75
|
+
|
76
|
+
should "find document by list of IDs" do
|
77
|
+
Configuration.client.expects(:post).returns(mock_response(@find_last_two.to_json))
|
78
|
+
documents = PersistentArticle.find 2, 3
|
79
|
+
|
80
|
+
assert_equal 2, documents.count
|
81
|
+
end
|
82
|
+
|
83
|
+
should "find document by array of IDs" do
|
84
|
+
Configuration.client.expects(:post).returns(mock_response(@find_last_two.to_json))
|
85
|
+
documents = PersistentArticle.find [2, 3]
|
86
|
+
|
87
|
+
assert_equal 2, documents.count
|
88
|
+
end
|
89
|
+
|
90
|
+
should "find all documents" do
|
91
|
+
Configuration.client.stubs(:post).returns(mock_response(@find_all.to_json))
|
92
|
+
documents = PersistentArticle.all
|
93
|
+
|
94
|
+
assert_equal 3, documents.count
|
95
|
+
assert_equal 'First', documents.first.attributes['title']
|
96
|
+
assert_equal PersistentArticle.find(:all).map { |e| e.id }, PersistentArticle.all.map { |e| e.id }
|
97
|
+
end
|
98
|
+
|
99
|
+
should "find first document" do
|
100
|
+
Configuration.client.expects(:post).returns(mock_response(@find_first.to_json))
|
101
|
+
document = PersistentArticle.first
|
102
|
+
|
103
|
+
assert_equal 'First', document.attributes['title']
|
104
|
+
end
|
105
|
+
|
106
|
+
should "raise error when passing incorrect argument" do
|
107
|
+
assert_raise(ArgumentError) do
|
108
|
+
PersistentArticle.find :name => 'Test'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
should_eventually "raise error when document is not found" do
|
113
|
+
assert_raise(DocumentNotFound) do
|
114
|
+
PersistentArticle.find 'xyz001'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
context "Persistent model" do
|
121
|
+
|
122
|
+
setup { @article = PersistentArticle.new :title => 'Test', :tags => [:one, :two] }
|
123
|
+
|
124
|
+
context "attribute methods" do
|
125
|
+
|
126
|
+
should "allow to set attributes on initialization" do
|
127
|
+
assert_not_nil @article.attributes
|
128
|
+
assert_equal 'Test', @article.attributes['title']
|
129
|
+
end
|
130
|
+
|
131
|
+
should "allow to leave attributes blank on initialization" do
|
132
|
+
assert_nothing_raised { PersistentArticle.new }
|
133
|
+
end
|
134
|
+
|
135
|
+
should "have getter methods for attributes" do
|
136
|
+
assert_not_nil @article.title
|
137
|
+
assert_equal 'Test', @article.title
|
138
|
+
assert_equal [:one, :two], @article.tags
|
139
|
+
end
|
140
|
+
|
141
|
+
should "have getter methods for attribute passed as a String" do
|
142
|
+
article = PersistentArticle.new 'title' => 'Tony Montana'
|
143
|
+
assert_not_nil article.title
|
144
|
+
assert_equal 'Tony Montana', article.title
|
145
|
+
end
|
146
|
+
|
147
|
+
should "raise error when getting unknown attribute" do
|
148
|
+
assert_raise(NoMethodError) do
|
149
|
+
@article.krapulitz
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
should "not raise error when getting unset attribute" do
|
154
|
+
article = PersistentArticle.new :title => 'Test'
|
155
|
+
|
156
|
+
assert_nothing_raised { article.published_on }
|
157
|
+
assert_nil article.published_on
|
158
|
+
end
|
159
|
+
|
160
|
+
should_eventually "return default value for attribute" do
|
161
|
+
article = PersistentArticle.new :title => 'Test'
|
162
|
+
article.class_eval do
|
163
|
+
property :title
|
164
|
+
property :tags, :default => []
|
165
|
+
end
|
166
|
+
|
167
|
+
assert_nothing_raised { article.tags }
|
168
|
+
assert_equal [], article.tags
|
169
|
+
end
|
170
|
+
|
171
|
+
should "have query method for attribute" do
|
172
|
+
assert_equal true, @article.title?
|
173
|
+
end
|
174
|
+
|
175
|
+
should "raise error when querying for unknown attribute" do
|
176
|
+
assert_raise(NoMethodError) do
|
177
|
+
@article.krapulitz?
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
should "not raise error when querying for unset attribute" do
|
182
|
+
article = PersistentArticle.new :title => 'Test'
|
183
|
+
|
184
|
+
assert_nothing_raised { article.published_on? }
|
185
|
+
assert ! article.published_on?
|
186
|
+
end
|
187
|
+
|
188
|
+
should "return true for respond_to? calls for set attributes" do
|
189
|
+
article = PersistentArticle.new :title => 'Test'
|
190
|
+
assert article.respond_to?(:title)
|
191
|
+
end
|
192
|
+
|
193
|
+
should "return false for respond_to? calls for unknown attributes" do
|
194
|
+
article = PersistentArticle.new :title => 'Test'
|
195
|
+
assert ! article.respond_to?(:krapulitz)
|
196
|
+
end
|
197
|
+
|
198
|
+
should "return true for respond_to? calls for defined but unset attributes" do
|
199
|
+
article = PersistentArticle.new :title => 'Test'
|
200
|
+
|
201
|
+
assert article.respond_to?(:published_on)
|
202
|
+
end
|
203
|
+
|
204
|
+
should "have attribute names" do
|
205
|
+
article = PersistentArticle.new :title => 'Test', :tags => ['one', 'two']
|
206
|
+
assert_equal ['published_on', 'tags', 'title'], article.attribute_names
|
207
|
+
end
|
208
|
+
|
209
|
+
should "have setter method for attribute" do
|
210
|
+
@article.title = 'Updated'
|
211
|
+
assert_equal 'Updated', @article.title
|
212
|
+
assert_equal 'Updated', @article.attributes['title']
|
213
|
+
end
|
214
|
+
|
215
|
+
should_eventually "allow to set deeply nested attributes on initialization" do
|
216
|
+
article = PersistentArticle.new :title => 'Test', :author => { :first_name => 'John', :last_name => 'Smith' }
|
217
|
+
|
218
|
+
assert_equal 'John', article.author.first_name
|
219
|
+
assert_equal 'Smith', article.author.last_name
|
220
|
+
assert_equal({ :first_name => 'John', :last_name => 'Smith' }, article.attributes['author'])
|
221
|
+
end
|
222
|
+
|
223
|
+
should_eventually "allow to set deeply nested attributes on update" do
|
224
|
+
article = PersistentArticle.new :title => 'Test', :author => { :first_name => 'John', :last_name => 'Smith' }
|
225
|
+
|
226
|
+
article.author.first_name = 'Robert'
|
227
|
+
article.author.last_name = 'Carpenter'
|
228
|
+
|
229
|
+
assert_equal 'Robert', article.author.first_name
|
230
|
+
assert_equal 'Carpenter', article.author.last_name
|
231
|
+
assert_equal({ :first_name => 'Robert', :last_name => 'Carpenter' }, article.attributes['author'])
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
context "when creating" do
|
237
|
+
|
238
|
+
should "save the document with generated ID in the database" do
|
239
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/persistent_articles/persistent_article/",
|
240
|
+
'{"title":"Test","tags":["one","two"],"published_on":null}').
|
241
|
+
returns(mock_response('{"ok":true,"_id":"abc123"}'))
|
242
|
+
article = PersistentArticle.create :title => 'Test', :tags => [:one, :two]
|
243
|
+
|
244
|
+
assert article.persisted?, "#{article.inspect} should be `persisted?`"
|
245
|
+
assert_equal 'abc123', article.id
|
246
|
+
end
|
247
|
+
|
248
|
+
should "save the document with custom ID in the database" do
|
249
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/persistent_articles/persistent_article/r2d2",
|
250
|
+
'{"title":"Test","id":"r2d2","tags":null,"published_on":null}').
|
251
|
+
returns(mock_response('{"ok":true,"_id":"r2d2"}'))
|
252
|
+
article = PersistentArticle.create :id => 'r2d2', :title => 'Test'
|
253
|
+
|
254
|
+
assert_equal 'r2d2', article.id
|
255
|
+
end
|
256
|
+
|
257
|
+
should "perform model validations" do
|
258
|
+
Configuration.client.expects(:post).never
|
259
|
+
|
260
|
+
assert ! ValidatedModel.create(:name => nil)
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
context "when creating" do
|
266
|
+
|
267
|
+
should "set the id property" do
|
268
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/persistent_articles/persistent_article/",
|
269
|
+
{:title => 'Test', :tags => nil, :published_on => nil}.to_json).
|
270
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
271
|
+
|
272
|
+
article = PersistentArticle.create :title => 'Test'
|
273
|
+
assert_equal '1', article.id
|
274
|
+
end
|
275
|
+
|
276
|
+
should "not set the id property if already set" do
|
277
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/persistent_articles/persistent_article/123",
|
278
|
+
'{"title":"Test","id":"123","tags":null,"published_on":null}').
|
279
|
+
returns(mock_response('{"ok":true, "_id":"XXX"}'))
|
280
|
+
|
281
|
+
article = PersistentArticle.create :id => '123', :title => 'Test'
|
282
|
+
assert_equal '123', article.id
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
context "when saving" do
|
288
|
+
|
289
|
+
should "save the document with updated attribute" do
|
290
|
+
article = PersistentArticle.new :id => 1, :title => 'Test'
|
291
|
+
|
292
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/persistent_articles/persistent_article/1",
|
293
|
+
'{"title":"Test","id":1,"tags":null,"published_on":null}').
|
294
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
295
|
+
assert article.save
|
296
|
+
|
297
|
+
article.title = 'Updated'
|
298
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/persistent_articles/persistent_article/1",
|
299
|
+
'{"title":"Updated","id":1,"tags":null,"published_on":null}').
|
300
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
301
|
+
assert article.save
|
302
|
+
end
|
303
|
+
|
304
|
+
should "perform validations" do
|
305
|
+
article = ValidatedModel.new :name => nil
|
306
|
+
assert ! article.save
|
307
|
+
end
|
308
|
+
|
309
|
+
should "set the id property" do
|
310
|
+
article = PersistentArticle.new
|
311
|
+
article.title = 'Test'
|
312
|
+
|
313
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/persistent_articles/persistent_article/",
|
314
|
+
article.to_indexed_json).
|
315
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
316
|
+
assert article.save
|
317
|
+
assert_equal '1', article.id
|
318
|
+
end
|
319
|
+
|
320
|
+
should "not set the id property if already set" do
|
321
|
+
article = PersistentArticle.new
|
322
|
+
article.id = '123'
|
323
|
+
article.title = 'Test'
|
324
|
+
|
325
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/persistent_articles/persistent_article/123",
|
326
|
+
'{"title":"Test","id":"123","tags":null,"published_on":null}').
|
327
|
+
returns(mock_response('{"ok":true,"_id":"XXX"}'))
|
328
|
+
assert article.save
|
329
|
+
assert_equal '123', article.id
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
|
334
|
+
context "when destroying" do
|
335
|
+
|
336
|
+
should "delete the document from the database" do
|
337
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/persistent_articles/persistent_article/123",
|
338
|
+
'{"title":"Test","id":"123","tags":null,"published_on":null}').
|
339
|
+
returns(mock_response('{"ok":true,"_id":"123"}'))
|
340
|
+
Configuration.client.expects(:delete).with("#{Configuration.url}/persistent_articles/persistent_article/123")
|
341
|
+
|
342
|
+
article = PersistentArticle.new :id => '123', :title => 'Test'
|
343
|
+
article.save
|
344
|
+
article.destroy
|
345
|
+
end
|
346
|
+
|
347
|
+
end
|
348
|
+
|
349
|
+
context "when updating attributes" do
|
350
|
+
|
351
|
+
should "update single attribute" do
|
352
|
+
@article.expects(:save).returns(true)
|
353
|
+
|
354
|
+
@article.update_attribute :title, 'Updated'
|
355
|
+
assert_equal 'Updated', @article.title
|
356
|
+
end
|
357
|
+
|
358
|
+
should "update all attributes" do
|
359
|
+
@article.expects(:save).returns(true)
|
360
|
+
|
361
|
+
@article.update_attributes :title => 'Updated', :tags => ['three']
|
362
|
+
assert_equal 'Updated', @article.title
|
363
|
+
assert_equal ['three'], @article.tags
|
364
|
+
end
|
365
|
+
|
366
|
+
end
|
367
|
+
|
368
|
+
end
|
369
|
+
|
370
|
+
context "Persistent model with mapping definition" do
|
371
|
+
|
372
|
+
should "create the index with mapping" do
|
373
|
+
expected_mapping = {
|
374
|
+
:mappings => { :persistent_article_with_mapping => {
|
375
|
+
:properties => { :title => { :type => 'string', :analyzer => 'snowball', :boost => 10 } }
|
376
|
+
}}
|
377
|
+
}
|
378
|
+
|
379
|
+
Tire::Index.any_instance.expects(:create).with(expected_mapping)
|
380
|
+
|
381
|
+
class ::PersistentArticleWithMapping
|
382
|
+
|
383
|
+
include Tire::Model::Persistence
|
384
|
+
include Tire::Model::Search
|
385
|
+
include Tire::Model::Callbacks
|
386
|
+
|
387
|
+
mapping do
|
388
|
+
property :title, :type => 'string', :analyzer => 'snowball', :boost => 10
|
389
|
+
end
|
390
|
+
|
391
|
+
end
|
392
|
+
|
393
|
+
assert_equal 'snowball', PersistentArticleWithMapping.mapping[:title][:analyzer]
|
394
|
+
end
|
395
|
+
|
396
|
+
end
|
397
|
+
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|