ssickles-tire 0.4.2.7 → 0.4.3
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/lib/tire.rb +18 -3
- data/lib/tire/alias.rb +11 -35
- data/lib/tire/index.rb +34 -76
- 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 +72 -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 +71 -0
- data/lib/tire/model/search.rb +305 -0
- data/lib/tire/results/collection.rb +38 -13
- data/lib/tire/results/item.rb +19 -0
- data/lib/tire/rubyext/hash.rb +8 -0
- data/lib/tire/rubyext/ruby_1_8.rb +54 -0
- data/lib/tire/rubyext/symbol.rb +11 -0
- data/lib/tire/search.rb +7 -8
- data/lib/tire/search/scan.rb +8 -8
- data/lib/tire/search/sort.rb +1 -1
- data/lib/tire/utils.rb +17 -0
- data/lib/tire/version.rb +7 -38
- 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/mongoid_searchable_test.rb +309 -0
- data/test/integration/persistent_model_test.rb +117 -0
- data/test/integration/reindex_test.rb +2 -2
- data/test/integration/scan_test.rb +1 -1
- 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/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 +27 -3
- data/test/unit/active_model_lint_test.rb +17 -0
- data/test/unit/index_alias_test.rb +3 -17
- data/test/unit/index_test.rb +30 -18
- 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 +516 -0
- data/test/unit/model_search_test.rb +899 -0
- data/test/unit/results_collection_test.rb +60 -0
- data/test/unit/results_item_test.rb +37 -0
- data/test/unit/rubyext_test.rb +3 -3
- data/test/unit/search_test.rb +1 -6
- data/test/unit/tire_test.rb +15 -0
- data/tire.gemspec +30 -13
- metadata +153 -41
- data/lib/tire/rubyext/to_json.rb +0 -21
@@ -12,15 +12,9 @@ module Tire
|
|
12
12
|
should "have a name and defaults" do
|
13
13
|
@alias = Alias.new :name => 'dummy'
|
14
14
|
assert_equal 'dummy', @alias.name
|
15
|
-
assert_equal Configuration.url, @alias.url
|
16
15
|
assert @alias.indices.empty?
|
17
16
|
end
|
18
17
|
|
19
|
-
should "have a configurable base URL" do
|
20
|
-
@alias = Alias.new :url => 'http://example.com:9200'
|
21
|
-
assert_equal 'http://example.com:9200', @alias.url
|
22
|
-
end
|
23
|
-
|
24
18
|
should "have indices" do
|
25
19
|
@alias = Alias.new :indices => ['index_A', 'index_B']
|
26
20
|
assert_equal ['index_A', 'index_B'], @alias.indices.to_a
|
@@ -100,7 +94,7 @@ module Tire
|
|
100
94
|
a['add']['alias'] == 'alias_martha'
|
101
95
|
end
|
102
96
|
end.returns(mock_response('{}'), 200)
|
103
|
-
|
97
|
+
|
104
98
|
a = Alias.find('alias_martha')
|
105
99
|
a.indices.push 'index_A'
|
106
100
|
a.save
|
@@ -115,7 +109,7 @@ module Tire
|
|
115
109
|
a['remove']['alias'] == 'alias_martha'
|
116
110
|
end
|
117
111
|
end.returns(mock_response('{}'), 200)
|
118
|
-
|
112
|
+
|
119
113
|
a = Alias.find('alias_martha')
|
120
114
|
a.indices.delete 'index_A'
|
121
115
|
a.save
|
@@ -126,7 +120,7 @@ module Tire
|
|
126
120
|
# puts json
|
127
121
|
MultiJson.decode(json)['actions'].all? { |a| a['add']['routing'] == 'martha' }
|
128
122
|
end.returns(mock_response('{}'), 200)
|
129
|
-
|
123
|
+
|
130
124
|
a = Alias.find('alias_martha')
|
131
125
|
a.routing('martha')
|
132
126
|
a.save
|
@@ -187,14 +181,6 @@ module Tire
|
|
187
181
|
a = Alias.find('alias_martha')
|
188
182
|
assert_instance_of Alias, a
|
189
183
|
assert_equal ['index_B', 'index_C'], a.indices.to_a.sort
|
190
|
-
|
191
|
-
Configuration.client.expects(:get).with do |url, json|
|
192
|
-
url == "http://example.com:9200/_aliases"
|
193
|
-
end.returns( mock_response( %q|{"index_A":{"aliases":{}},"index_B":{"aliases":{"alias_john":{"filter":{"term":{"user":"john"}}},"alias_martha":{"filter":{"term":{"user":"martha"}}}}},"index_C":{"aliases":{"alias_martha":{"filter":{"term":{"user":"martha"}}}}}}|), 200 )
|
194
|
-
|
195
|
-
a = Alias.find('http://example.com:9200', 'alias_martha')
|
196
|
-
assert_instance_of Alias, a
|
197
|
-
assert_equal ['index_B', 'index_C'], a.indices.to_a.sort
|
198
184
|
end
|
199
185
|
|
200
186
|
should "find an alias and configure it with a block" do
|
data/test/unit/index_test.rb
CHANGED
@@ -18,11 +18,6 @@ module Tire
|
|
18
18
|
assert_equal "#{Configuration.url}/#{@index.name}", @index.url
|
19
19
|
end
|
20
20
|
|
21
|
-
should "have a configurable URL endpoint" do
|
22
|
-
@index.url = 'http://example.com'
|
23
|
-
assert_equal "http://example.com/#{@index.name}", @index.url
|
24
|
-
end
|
25
|
-
|
26
21
|
should "return HTTP response" do
|
27
22
|
assert_respond_to @index, :response
|
28
23
|
|
@@ -216,21 +211,21 @@ module Tire
|
|
216
211
|
|
217
212
|
should "set type from Hash :type property" do
|
218
213
|
Configuration.client.expects(:post).with do |url,document|
|
219
|
-
url == "#{@index.url}/article"
|
214
|
+
url == "#{@index.url}/article/"
|
220
215
|
end.returns(mock_response('{"ok":true,"_id":"test"}'))
|
221
216
|
@index.store :type => 'article', :title => 'Test'
|
222
217
|
end
|
223
218
|
|
224
219
|
should "set type from Hash :_type property" do
|
225
220
|
Configuration.client.expects(:post).with do |url,document|
|
226
|
-
url == "#{@index.url}/article"
|
221
|
+
url == "#{@index.url}/article/"
|
227
222
|
end.returns(mock_response('{"ok":true,"_id":"test"}'))
|
228
223
|
@index.store :_type => 'article', :title => 'Test'
|
229
224
|
end
|
230
225
|
|
231
226
|
should "set type from Object _type method" do
|
232
227
|
Configuration.client.expects(:post).with do |url,document|
|
233
|
-
url == "#{@index.url}/article"
|
228
|
+
url == "#{@index.url}/article/"
|
234
229
|
end.returns(mock_response('{"ok":true,"_id":"test"}'))
|
235
230
|
|
236
231
|
article = Class.new do
|
@@ -242,7 +237,7 @@ module Tire
|
|
242
237
|
|
243
238
|
should "set type from Object type method" do
|
244
239
|
Configuration.client.expects(:post).with do |url,document|
|
245
|
-
url == "#{@index.url}/article"
|
240
|
+
url == "#{@index.url}/article/"
|
246
241
|
end.returns(mock_response('{"ok":true,"_id":"test"}'))
|
247
242
|
|
248
243
|
article = Class.new do
|
@@ -254,7 +249,7 @@ module Tire
|
|
254
249
|
|
255
250
|
should "properly encode namespaced document types" do
|
256
251
|
Configuration.client.expects(:post).with do |url,document|
|
257
|
-
url == "#{@index.url}/my_namespace%2Fmy_model"
|
252
|
+
url == "#{@index.url}/my_namespace%2Fmy_model/"
|
258
253
|
end.returns(mock_response('{"ok":true,"_id":"123"}'))
|
259
254
|
|
260
255
|
module MyNamespace
|
@@ -268,7 +263,7 @@ module Tire
|
|
268
263
|
end
|
269
264
|
|
270
265
|
should "set default type" do
|
271
|
-
Configuration.client.expects(:post).with("#{@index.url}/document", '{"title":"Test"}').returns(mock_response('{"ok":true,"_id":"test"}'))
|
266
|
+
Configuration.client.expects(:post).with("#{@index.url}/document/", '{"title":"Test"}').returns(mock_response('{"ok":true,"_id":"test"}'))
|
272
267
|
@index.store :title => 'Test'
|
273
268
|
end
|
274
269
|
|
@@ -315,7 +310,7 @@ module Tire
|
|
315
310
|
Configuration.reset :wrapper
|
316
311
|
|
317
312
|
Configuration.client.stubs(:post).with do |url, payload|
|
318
|
-
url == "#{@index.url}/article" &&
|
313
|
+
url == "#{@index.url}/article/" &&
|
319
314
|
payload =~ /"title":"Test"/
|
320
315
|
end.
|
321
316
|
returns(mock_response('{"ok":true,"_id":"id-1"}'))
|
@@ -452,6 +447,23 @@ module Tire
|
|
452
447
|
@index.bulk_store [ {:id => '1', :title => 'One'}, {:id => '2', :title => 'Two'} ]
|
453
448
|
end
|
454
449
|
|
450
|
+
should "serialize ActiveModel instances" do
|
451
|
+
Configuration.client.expects(:post).with do |url, json|
|
452
|
+
url == "#{ActiveModelArticle.index.url}/_bulk" &&
|
453
|
+
json =~ /"_index":"active_model_articles"/ &&
|
454
|
+
json =~ /"_type":"active_model_article"/ &&
|
455
|
+
json =~ /"_id":"1"/ &&
|
456
|
+
json =~ /"_id":"2"/ &&
|
457
|
+
json =~ /"title":"One"/ &&
|
458
|
+
json =~ /"title":"Two"/
|
459
|
+
end.returns(mock_response('{}', 200))
|
460
|
+
|
461
|
+
one = ActiveModelArticle.new 'title' => 'One'; one.id = '1'
|
462
|
+
two = ActiveModelArticle.new 'title' => 'Two'; two.id = '2'
|
463
|
+
|
464
|
+
ActiveModelArticle.index.bulk_store [ one, two ]
|
465
|
+
end
|
466
|
+
|
455
467
|
context "namespaced models" do
|
456
468
|
should "not URL-escape the document_type" do
|
457
469
|
Configuration.client.expects(:post).with do |url, json|
|
@@ -502,13 +514,13 @@ module Tire
|
|
502
514
|
|
503
515
|
should "display error message when collection item does not have ID" do
|
504
516
|
Configuration.client.expects(:post).with do |url, json|
|
505
|
-
url == "#{
|
517
|
+
url == "#{ActiveModelArticle.index.url}/_bulk"
|
506
518
|
end.returns(mock_response('success', 200))
|
507
519
|
|
508
520
|
STDERR.expects(:puts).once
|
509
521
|
|
510
522
|
documents = [ { :title => 'Bogus' }, { :title => 'Real', :id => 1 } ]
|
511
|
-
|
523
|
+
ActiveModelArticle.index.bulk_store documents
|
512
524
|
end
|
513
525
|
|
514
526
|
end
|
@@ -733,7 +745,7 @@ module Tire
|
|
733
745
|
should "percolate document against all registered queries" do
|
734
746
|
Configuration.client.expects(:post).
|
735
747
|
with do |url, payload|
|
736
|
-
url == "#{@index.url}/article
|
748
|
+
url == "#{@index.url}/article/?percolate=*" &&
|
737
749
|
payload =~ /"title":"Test"/
|
738
750
|
end.
|
739
751
|
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
@@ -743,7 +755,7 @@ module Tire
|
|
743
755
|
should "percolate document against specific queries" do
|
744
756
|
Configuration.client.expects(:post).
|
745
757
|
with do |url, payload|
|
746
|
-
url == "#{@index.url}/article
|
758
|
+
url == "#{@index.url}/article/?percolate=tag:alerts" &&
|
747
759
|
payload =~ /"title":"Test"/
|
748
760
|
end.
|
749
761
|
returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
|
@@ -811,10 +823,10 @@ module Tire
|
|
811
823
|
def index_something
|
812
824
|
@tags = ['block', 'scope', 'revenge']
|
813
825
|
|
814
|
-
Index.any_instance.expects(:store).with(:
|
826
|
+
Index.any_instance.expects(:store).with(title: 'Title From Outer Space', tags: ['block', 'scope', 'revenge'])
|
815
827
|
|
816
828
|
Tire::Index.new 'outer-space' do |index|
|
817
|
-
index.store
|
829
|
+
index.store title: @my_title, tags: @tags
|
818
830
|
end
|
819
831
|
end
|
820
832
|
|
@@ -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,516 @@
|
|
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 creating" do
|
311
|
+
|
312
|
+
should "save the document with generated ID in the database" do
|
313
|
+
Configuration.client.expects(:post).
|
314
|
+
with do |url, payload|
|
315
|
+
doc = MultiJson.decode(payload)
|
316
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/" &&
|
317
|
+
doc['title'] == 'Test' &&
|
318
|
+
doc['tags'] == ['one', 'two']
|
319
|
+
doc['published_on'] == nil
|
320
|
+
end.
|
321
|
+
returns(mock_response('{"ok":true,"_id":"abc123"}'))
|
322
|
+
article = PersistentArticle.create :title => 'Test', :tags => [:one, :two]
|
323
|
+
|
324
|
+
assert article.persisted?, "#{article.inspect} should be `persisted?`"
|
325
|
+
assert_equal 'abc123', article.id
|
326
|
+
end
|
327
|
+
|
328
|
+
should "save the document with custom ID in the database" do
|
329
|
+
Configuration.client.expects(:post).
|
330
|
+
with do |url, payload|
|
331
|
+
doc = MultiJson.decode(payload)
|
332
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/r2d2" &&
|
333
|
+
doc['title'] == 'Test' &&
|
334
|
+
doc['published_on'] == nil
|
335
|
+
end.
|
336
|
+
returns(mock_response('{"ok":true,"_id":"r2d2"}'))
|
337
|
+
article = PersistentArticle.create :id => 'r2d2', :title => 'Test'
|
338
|
+
|
339
|
+
assert_equal 'r2d2', article.id
|
340
|
+
end
|
341
|
+
|
342
|
+
should "perform model validations" do
|
343
|
+
Configuration.client.expects(:post).never
|
344
|
+
|
345
|
+
assert ! ValidatedModel.create(:name => nil)
|
346
|
+
end
|
347
|
+
|
348
|
+
end
|
349
|
+
|
350
|
+
context "when creating" do
|
351
|
+
|
352
|
+
should "set the id property" do
|
353
|
+
Configuration.client.expects(:post).
|
354
|
+
with do |url, payload|
|
355
|
+
doc = MultiJson.decode(payload)
|
356
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/" &&
|
357
|
+
doc['title'] == 'Test'
|
358
|
+
end.
|
359
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
360
|
+
|
361
|
+
article = PersistentArticle.create :title => 'Test'
|
362
|
+
assert_equal '1', article.id
|
363
|
+
end
|
364
|
+
|
365
|
+
should "not set the id property if already set" do
|
366
|
+
Configuration.client.expects(:post).
|
367
|
+
with do |url, payload|
|
368
|
+
doc = MultiJson.decode(payload)
|
369
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/123" &&
|
370
|
+
doc['title'] == 'Test' &&
|
371
|
+
doc['published_on'] == nil
|
372
|
+
end.
|
373
|
+
returns(mock_response('{"ok":true, "_id":"XXX"}'))
|
374
|
+
|
375
|
+
article = PersistentArticle.create :id => '123', :title => 'Test'
|
376
|
+
assert_equal '123', article.id
|
377
|
+
end
|
378
|
+
|
379
|
+
end
|
380
|
+
|
381
|
+
context "when saving" do
|
382
|
+
|
383
|
+
should "save the document with updated attribute" do
|
384
|
+
article = PersistentArticle.new :id => '1', :title => 'Test'
|
385
|
+
|
386
|
+
Configuration.client.expects(:post).
|
387
|
+
with do |url, payload|
|
388
|
+
doc = MultiJson.decode(payload)
|
389
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/1" &&
|
390
|
+
doc['title'] == 'Test' &&
|
391
|
+
doc['published_on'] == nil
|
392
|
+
end.
|
393
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
394
|
+
assert article.save
|
395
|
+
|
396
|
+
article.title = 'Updated'
|
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'] == 'Updated'
|
403
|
+
end.
|
404
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
405
|
+
assert article.save
|
406
|
+
end
|
407
|
+
|
408
|
+
should "perform validations" do
|
409
|
+
article = ValidatedModel.new :name => nil
|
410
|
+
assert ! article.save
|
411
|
+
end
|
412
|
+
|
413
|
+
should "set the id property itself" do
|
414
|
+
article = PersistentArticle.new
|
415
|
+
article.title = 'Test'
|
416
|
+
|
417
|
+
Configuration.client.expects(:post).with("#{Configuration.url}/persistent_articles/persistent_article/",
|
418
|
+
article.to_indexed_json).
|
419
|
+
returns(mock_response('{"ok":true,"_id":"1"}'))
|
420
|
+
assert article.save
|
421
|
+
assert_equal '1', article.id
|
422
|
+
end
|
423
|
+
|
424
|
+
should "not set the id property if already set" do
|
425
|
+
article = PersistentArticle.new
|
426
|
+
article.id = '456'
|
427
|
+
article.title = 'Test'
|
428
|
+
|
429
|
+
Configuration.client.expects(:post).
|
430
|
+
with do |url, payload|
|
431
|
+
doc = MultiJson.decode(payload)
|
432
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/456" &&
|
433
|
+
doc['title'] == 'Test'
|
434
|
+
end.
|
435
|
+
returns(mock_response('{"ok":true,"_id":"XXX"}'))
|
436
|
+
assert article.save
|
437
|
+
assert_equal '456', article.id
|
438
|
+
end
|
439
|
+
|
440
|
+
end
|
441
|
+
|
442
|
+
context "when destroying" do
|
443
|
+
|
444
|
+
should "delete the document from the database" do
|
445
|
+
Configuration.client.expects(:post).
|
446
|
+
with do |url, payload|
|
447
|
+
doc = MultiJson.decode(payload)
|
448
|
+
url == "#{Configuration.url}/persistent_articles/persistent_article/123" &&
|
449
|
+
doc['title'] == 'Test'
|
450
|
+
end.returns(mock_response('{"ok":true,"_id":"123"}'))
|
451
|
+
|
452
|
+
Configuration.client.expects(:delete).
|
453
|
+
with("#{Configuration.url}/persistent_articles/persistent_article/123").
|
454
|
+
returns(mock_response('{"ok":true,"acknowledged":true}', 200))
|
455
|
+
|
456
|
+
article = PersistentArticle.new :id => '123', :title => 'Test'
|
457
|
+
article.save
|
458
|
+
article.destroy
|
459
|
+
end
|
460
|
+
|
461
|
+
end
|
462
|
+
|
463
|
+
context "when updating attributes" do
|
464
|
+
|
465
|
+
should "update single attribute" do
|
466
|
+
@article.expects(:save).returns(true)
|
467
|
+
|
468
|
+
@article.update_attribute :title, 'Updated'
|
469
|
+
assert_equal 'Updated', @article.title
|
470
|
+
end
|
471
|
+
|
472
|
+
should "update all attributes" do
|
473
|
+
@article.expects(:save).returns(true)
|
474
|
+
|
475
|
+
@article.update_attributes :title => 'Updated', :tags => ['three']
|
476
|
+
assert_equal 'Updated', @article.title
|
477
|
+
assert_equal ['three'], @article.tags
|
478
|
+
end
|
479
|
+
|
480
|
+
end
|
481
|
+
|
482
|
+
end
|
483
|
+
|
484
|
+
context "Persistent model with mapping definition" do
|
485
|
+
|
486
|
+
should "create the index with mapping" do
|
487
|
+
expected = {
|
488
|
+
:settings => {},
|
489
|
+
:mappings => { :persistent_article_with_mapping => {
|
490
|
+
:properties => { :title => { :type => 'string', :analyzer => 'snowball', :boost => 10 } }
|
491
|
+
}}
|
492
|
+
}
|
493
|
+
|
494
|
+
Tire::Index.any_instance.stubs(:exists?).returns(false)
|
495
|
+
Tire::Index.any_instance.expects(:create).with(expected)
|
496
|
+
|
497
|
+
class ::PersistentArticleWithMapping
|
498
|
+
|
499
|
+
include Tire::Model::Persistence
|
500
|
+
include Tire::Model::Search
|
501
|
+
include Tire::Model::Callbacks
|
502
|
+
|
503
|
+
mapping do
|
504
|
+
property :title, :type => 'string', :analyzer => 'snowball', :boost => 10
|
505
|
+
end
|
506
|
+
|
507
|
+
end
|
508
|
+
|
509
|
+
assert_equal 'snowball', PersistentArticleWithMapping.mapping[:title][:analyzer]
|
510
|
+
end
|
511
|
+
|
512
|
+
end
|
513
|
+
|
514
|
+
end
|
515
|
+
end
|
516
|
+
end
|