will-couchrest 0.32.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/LICENSE +176 -0
- data/README.md +165 -0
- data/Rakefile +74 -0
- data/THANKS.md +18 -0
- data/examples/model/example.rb +144 -0
- data/examples/word_count/markov +38 -0
- data/examples/word_count/views/books/chunked-map.js +3 -0
- data/examples/word_count/views/books/united-map.js +1 -0
- data/examples/word_count/views/markov/chain-map.js +6 -0
- data/examples/word_count/views/markov/chain-reduce.js +7 -0
- data/examples/word_count/views/word_count/count-map.js +6 -0
- data/examples/word_count/views/word_count/count-reduce.js +3 -0
- data/examples/word_count/word_count.rb +46 -0
- data/examples/word_count/word_count_query.rb +40 -0
- data/examples/word_count/word_count_views.rb +26 -0
- data/history.txt +65 -0
- data/lib/couchrest.rb +199 -0
- data/lib/couchrest/commands/generate.rb +71 -0
- data/lib/couchrest/commands/push.rb +103 -0
- data/lib/couchrest/core/adapters/restclient.rb +35 -0
- data/lib/couchrest/core/database.rb +317 -0
- data/lib/couchrest/core/design.rb +79 -0
- data/lib/couchrest/core/document.rb +83 -0
- data/lib/couchrest/core/http_abstraction.rb +48 -0
- data/lib/couchrest/core/response.rb +16 -0
- data/lib/couchrest/core/server.rb +88 -0
- data/lib/couchrest/core/view.rb +4 -0
- data/lib/couchrest/helper/pager.rb +103 -0
- data/lib/couchrest/helper/streamer.rb +44 -0
- data/lib/couchrest/helper/upgrade.rb +51 -0
- data/lib/couchrest/mixins.rb +4 -0
- data/lib/couchrest/mixins/attachments.rb +31 -0
- data/lib/couchrest/mixins/callbacks.rb +483 -0
- data/lib/couchrest/mixins/class_proxy.rb +116 -0
- data/lib/couchrest/mixins/collection.rb +225 -0
- data/lib/couchrest/mixins/design_doc.rb +103 -0
- data/lib/couchrest/mixins/document_queries.rb +82 -0
- data/lib/couchrest/mixins/extended_attachments.rb +74 -0
- data/lib/couchrest/mixins/extended_document_mixins.rb +8 -0
- data/lib/couchrest/mixins/properties.rb +158 -0
- data/lib/couchrest/mixins/validation.rb +257 -0
- data/lib/couchrest/mixins/views.rb +173 -0
- data/lib/couchrest/monkeypatches.rb +113 -0
- data/lib/couchrest/more/casted_model.rb +29 -0
- data/lib/couchrest/more/extended_document.rb +246 -0
- data/lib/couchrest/more/property.rb +40 -0
- data/lib/couchrest/support/blank.rb +42 -0
- data/lib/couchrest/support/class.rb +176 -0
- data/lib/couchrest/support/rails.rb +35 -0
- data/lib/couchrest/validation/auto_validate.rb +161 -0
- data/lib/couchrest/validation/contextual_validators.rb +78 -0
- data/lib/couchrest/validation/validation_errors.rb +125 -0
- data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
- data/lib/couchrest/validation/validators/confirmation_validator.rb +99 -0
- data/lib/couchrest/validation/validators/format_validator.rb +117 -0
- data/lib/couchrest/validation/validators/formats/email.rb +66 -0
- data/lib/couchrest/validation/validators/formats/url.rb +43 -0
- data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
- data/lib/couchrest/validation/validators/length_validator.rb +134 -0
- data/lib/couchrest/validation/validators/method_validator.rb +89 -0
- data/lib/couchrest/validation/validators/numeric_validator.rb +104 -0
- data/lib/couchrest/validation/validators/required_field_validator.rb +109 -0
- data/spec/couchrest/core/couchrest_spec.rb +201 -0
- data/spec/couchrest/core/database_spec.rb +700 -0
- data/spec/couchrest/core/design_spec.rb +138 -0
- data/spec/couchrest/core/document_spec.rb +267 -0
- data/spec/couchrest/core/server_spec.rb +35 -0
- data/spec/couchrest/helpers/pager_spec.rb +122 -0
- data/spec/couchrest/helpers/streamer_spec.rb +23 -0
- data/spec/couchrest/more/casted_extended_doc_spec.rb +75 -0
- data/spec/couchrest/more/casted_model_spec.rb +177 -0
- data/spec/couchrest/more/extended_doc_attachment_spec.rb +135 -0
- data/spec/couchrest/more/extended_doc_spec.rb +588 -0
- data/spec/couchrest/more/extended_doc_subclass_spec.rb +98 -0
- data/spec/couchrest/more/extended_doc_view_spec.rb +426 -0
- data/spec/couchrest/more/property_spec.rb +169 -0
- data/spec/fixtures/attachments/README +3 -0
- data/spec/fixtures/attachments/couchdb.png +0 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/more/article.rb +34 -0
- data/spec/fixtures/more/card.rb +22 -0
- data/spec/fixtures/more/cat.rb +18 -0
- data/spec/fixtures/more/course.rb +14 -0
- data/spec/fixtures/more/event.rb +6 -0
- data/spec/fixtures/more/invoice.rb +17 -0
- data/spec/fixtures/more/person.rb +8 -0
- data/spec/fixtures/more/question.rb +6 -0
- data/spec/fixtures/more/service.rb +12 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +37 -0
- data/utils/remap.rb +27 -0
- data/utils/subset.rb +30 -0
- metadata +198 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe CouchRest::Streamer do
|
|
4
|
+
before(:all) do
|
|
5
|
+
@cr = CouchRest.new(COUCHHOST)
|
|
6
|
+
@db = @cr.database(TESTDB)
|
|
7
|
+
@db.delete! rescue nil
|
|
8
|
+
@db = @cr.create_db(TESTDB) rescue nil
|
|
9
|
+
@streamer = CouchRest::Streamer.new(@db)
|
|
10
|
+
@docs = (1..1000).collect{|i| {:integer => i, :string => i.to_s}}
|
|
11
|
+
@db.bulk_save(@docs)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "should yield each row in a view" do
|
|
15
|
+
count = 0
|
|
16
|
+
sum = 0
|
|
17
|
+
@streamer.view("_all_docs") do |row|
|
|
18
|
+
count += 1
|
|
19
|
+
end
|
|
20
|
+
count.should == 1001
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
|
2
|
+
require File.join(FIXTURE_PATH, 'more', 'card')
|
|
3
|
+
|
|
4
|
+
class Car < CouchRest::ExtendedDocument
|
|
5
|
+
use_database TEST_SERVER.default_database
|
|
6
|
+
|
|
7
|
+
property :name
|
|
8
|
+
property :driver, :cast_as => 'Driver'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Driver < CouchRest::ExtendedDocument
|
|
12
|
+
use_database TEST_SERVER.default_database
|
|
13
|
+
# You have to add a casted_by accessor if you want to reach a casted extended doc parent
|
|
14
|
+
attr_accessor :casted_by
|
|
15
|
+
|
|
16
|
+
property :name
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe "casting an extended document" do
|
|
20
|
+
|
|
21
|
+
before(:each) do
|
|
22
|
+
@driver = Driver.new(:name => 'Matt')
|
|
23
|
+
@car = Car.new(:name => 'Renault 306', :driver => @driver)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "should retain all properties of the casted attribute" do
|
|
27
|
+
@car.driver.should == @driver
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "should let the casted document know who casted it" do
|
|
31
|
+
@car.driver.casted_by.should == @car
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe "assigning a value to casted attribute after initializing an object" do
|
|
36
|
+
|
|
37
|
+
before(:each) do
|
|
38
|
+
@car = Car.new(:name => 'Renault 306')
|
|
39
|
+
@driver = Driver.new(:name => 'Matt')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "should not create an empty casted object" do
|
|
43
|
+
@car.driver.should be_nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Note that this isn't casting the attribute, it's just assigning it a value
|
|
47
|
+
# (see "should not cast attribute")
|
|
48
|
+
it "should let you assign the value" do
|
|
49
|
+
@car.driver = @driver
|
|
50
|
+
@car.driver.name.should == 'Matt'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "should not cast attribute" do
|
|
54
|
+
@car.driver = JSON.parse(JSON.generate(@driver))
|
|
55
|
+
@car.driver.should_not be_instance_of(Driver)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe "casting an extended document from parsed JSON" do
|
|
61
|
+
|
|
62
|
+
before(:each) do
|
|
63
|
+
@driver = Driver.new(:name => 'Matt')
|
|
64
|
+
@car = Car.new(:name => 'Renault 306', :driver => @driver)
|
|
65
|
+
@new_car = Car.new(JSON.parse(JSON.generate(@car)))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "should cast casted attribute" do
|
|
69
|
+
@new_car.driver.should be_instance_of(Driver)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "should retain all properties of the casted attribute" do
|
|
73
|
+
@new_car.driver.should == @driver
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
|
4
|
+
require File.join(FIXTURE_PATH, 'more', 'card')
|
|
5
|
+
require File.join(FIXTURE_PATH, 'more', 'cat')
|
|
6
|
+
require File.join(FIXTURE_PATH, 'more', 'person')
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WithCastedModelMixin < Hash
|
|
10
|
+
include CouchRest::CastedModel
|
|
11
|
+
property :name
|
|
12
|
+
property :no_value
|
|
13
|
+
property :details, :default => {}
|
|
14
|
+
property :casted_attribute, :cast_as => 'WithCastedModelMixin'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class DummyModel < CouchRest::ExtendedDocument
|
|
18
|
+
use_database TEST_SERVER.default_database
|
|
19
|
+
raise "Default DB not set" if TEST_SERVER.default_database.nil?
|
|
20
|
+
property :casted_attribute, :cast_as => 'WithCastedModelMixin'
|
|
21
|
+
property :keywords, :cast_as => ["String"]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe CouchRest::CastedModel do
|
|
25
|
+
|
|
26
|
+
describe "A non hash class including CastedModel" do
|
|
27
|
+
it "should fail raising and include error" do
|
|
28
|
+
lambda do
|
|
29
|
+
class NotAHashButWithCastedModelMixin
|
|
30
|
+
include CouchRest::CastedModel
|
|
31
|
+
property :name
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end.should raise_error
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "isolated" do
|
|
39
|
+
before(:each) do
|
|
40
|
+
@obj = WithCastedModelMixin.new
|
|
41
|
+
end
|
|
42
|
+
it "should automatically include the property mixin and define getters and setters" do
|
|
43
|
+
@obj.name = 'Matt'
|
|
44
|
+
@obj.name.should == 'Matt'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "should allow override of default" do
|
|
48
|
+
@obj = WithCastedModelMixin.new(:name => 'Eric', :details => {'color' => 'orange'})
|
|
49
|
+
@obj.name.should == 'Eric'
|
|
50
|
+
@obj.details['color'].should == 'orange'
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "casted as an attribute, but without a value" do
|
|
55
|
+
before(:each) do
|
|
56
|
+
@obj = DummyModel.new
|
|
57
|
+
@casted_obj = @obj.casted_attribute
|
|
58
|
+
end
|
|
59
|
+
it "should be nil" do
|
|
60
|
+
@casted_obj.should == nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "casted as attribute" do
|
|
65
|
+
before(:each) do
|
|
66
|
+
casted = {:name => 'not whatever'}
|
|
67
|
+
@obj = DummyModel.new(:casted_attribute => {:name => 'whatever', :casted_attribute => casted})
|
|
68
|
+
@casted_obj = @obj.casted_attribute
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should be available from its parent" do
|
|
72
|
+
@casted_obj.should be_an_instance_of(WithCastedModelMixin)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "should have the getters defined" do
|
|
76
|
+
@casted_obj.name.should == 'whatever'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "should know who casted it" do
|
|
80
|
+
@casted_obj.casted_by.should == @obj
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "should return nil for the 'no_value' attribute" do
|
|
84
|
+
@casted_obj.no_value.should be_nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "should return nil for the unknown attribute" do
|
|
88
|
+
@casted_obj["unknown"].should be_nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "should return {} for the hash attribute" do
|
|
92
|
+
@casted_obj.details.should == {}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "should cast its own attributes" do
|
|
96
|
+
@casted_obj.casted_attribute.should be_instance_of(WithCastedModelMixin)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "casted as an array of a different type" do
|
|
101
|
+
before(:each) do
|
|
102
|
+
@obj = DummyModel.new(:keywords => ['couch', 'sofa', 'relax', 'canapé'])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "should cast the array propery" do
|
|
106
|
+
@obj.keywords.should be_an_instance_of(Array)
|
|
107
|
+
@obj.keywords.first.should == 'couch'
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
describe "saved document with casted models" do
|
|
113
|
+
before(:each) do
|
|
114
|
+
reset_test_db!
|
|
115
|
+
@obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
|
|
116
|
+
@obj.save.should be_true
|
|
117
|
+
@obj = DummyModel.get(@obj.id)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "should be able to load with the casted models" do
|
|
121
|
+
casted_obj = @obj.casted_attribute
|
|
122
|
+
casted_obj.should_not be_nil
|
|
123
|
+
casted_obj.should be_an_instance_of(WithCastedModelMixin)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "should have defined getters for the casted model" do
|
|
127
|
+
casted_obj = @obj.casted_attribute
|
|
128
|
+
casted_obj.name.should == "whatever"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "should have defined setters for the casted model" do
|
|
132
|
+
casted_obj = @obj.casted_attribute
|
|
133
|
+
casted_obj.name = "test"
|
|
134
|
+
casted_obj.name.should == "test"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "should retain an override of a casted model attribute's default" do
|
|
138
|
+
casted_obj = @obj.casted_attribute
|
|
139
|
+
casted_obj.details['color'] = 'orange'
|
|
140
|
+
@obj.save
|
|
141
|
+
casted_obj = DummyModel.get(@obj.id).casted_attribute
|
|
142
|
+
casted_obj.details['color'].should == 'orange'
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
describe "saving document with array of casted models and validation" do
|
|
148
|
+
before :each do
|
|
149
|
+
@cat = Cat.new
|
|
150
|
+
@cat.save
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "should save" do
|
|
154
|
+
toy = CatToy.new :name => "Mouse"
|
|
155
|
+
@cat.toys.push(toy)
|
|
156
|
+
@cat.save.should be_true
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "should fail because name is not present" do
|
|
160
|
+
toy = CatToy.new
|
|
161
|
+
@cat.toys.push(toy)
|
|
162
|
+
@cat.should_not be_valid
|
|
163
|
+
@cat.save.should be_false
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it "should not fail if the casted model doesn't have validation" do
|
|
167
|
+
Cat.property :masters, :cast_as => ['Person'], :default => []
|
|
168
|
+
Cat.validates_present :name
|
|
169
|
+
cat = Cat.new(:name => 'kitty')
|
|
170
|
+
cat.should be_valid
|
|
171
|
+
cat.masters.push Person.new
|
|
172
|
+
cat.should be_valid
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe "ExtendedDocument attachments" do
|
|
4
|
+
|
|
5
|
+
describe "#has_attachment?" do
|
|
6
|
+
before(:each) do
|
|
7
|
+
reset_test_db!
|
|
8
|
+
@obj = Basic.new
|
|
9
|
+
@obj.save.should == true
|
|
10
|
+
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
|
11
|
+
@attachment_name = 'my_attachment'
|
|
12
|
+
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'should return false if there is no attachment' do
|
|
16
|
+
@obj.has_attachment?('bogus').should be_false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'should return true if there is an attachment' do
|
|
20
|
+
@obj.has_attachment?(@attachment_name).should be_true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'should return true if an object with an attachment is reloaded' do
|
|
24
|
+
@obj.save.should be_true
|
|
25
|
+
reloaded_obj = Basic.get(@obj.id)
|
|
26
|
+
reloaded_obj.has_attachment?(@attachment_name).should be_true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'should return false if an attachment has been removed' do
|
|
30
|
+
@obj.delete_attachment(@attachment_name)
|
|
31
|
+
@obj.has_attachment?(@attachment_name).should be_false
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe "creating an attachment" do
|
|
36
|
+
before(:each) do
|
|
37
|
+
@obj = Basic.new
|
|
38
|
+
@obj.save.should == true
|
|
39
|
+
@file_ext = File.open(FIXTURE_PATH + '/attachments/test.html')
|
|
40
|
+
@file_no_ext = File.open(FIXTURE_PATH + '/attachments/README')
|
|
41
|
+
@attachment_name = 'my_attachment'
|
|
42
|
+
@content_type = 'media/mp3'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "should create an attachment from file with an extension" do
|
|
46
|
+
@obj.create_attachment(:file => @file_ext, :name => @attachment_name)
|
|
47
|
+
@obj.save.should == true
|
|
48
|
+
reloaded_obj = Basic.get(@obj.id)
|
|
49
|
+
reloaded_obj['_attachments'][@attachment_name].should_not be_nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should create an attachment from file without an extension" do
|
|
53
|
+
@obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
|
|
54
|
+
@obj.save.should == true
|
|
55
|
+
reloaded_obj = Basic.get(@obj.id)
|
|
56
|
+
reloaded_obj['_attachments'][@attachment_name].should_not be_nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'should raise ArgumentError if :file is missing' do
|
|
60
|
+
lambda{ @obj.create_attachment(:name => @attachment_name) }.should raise_error
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'should raise ArgumentError if :name is missing' do
|
|
64
|
+
lambda{ @obj.create_attachment(:file => @file_ext) }.should raise_error
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'should set the content-type if passed' do
|
|
68
|
+
@obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
|
|
69
|
+
@obj['_attachments'][@attachment_name]['content-type'].should == @content_type
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe 'reading, updating, and deleting an attachment' do
|
|
74
|
+
before(:each) do
|
|
75
|
+
@obj = Basic.new
|
|
76
|
+
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
|
77
|
+
@attachment_name = 'my_attachment'
|
|
78
|
+
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
|
79
|
+
@obj.save.should == true
|
|
80
|
+
@file.rewind
|
|
81
|
+
@content_type = 'media/mp3'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'should read an attachment that exists' do
|
|
85
|
+
@obj.read_attachment(@attachment_name).should == @file.read
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'should update an attachment that exists' do
|
|
89
|
+
file = File.open(FIXTURE_PATH + '/attachments/README')
|
|
90
|
+
@file.should_not == file
|
|
91
|
+
@obj.update_attachment(:file => file, :name => @attachment_name)
|
|
92
|
+
@obj.save
|
|
93
|
+
reloaded_obj = Basic.get(@obj.id)
|
|
94
|
+
file.rewind
|
|
95
|
+
reloaded_obj.read_attachment(@attachment_name).should_not == @file.read
|
|
96
|
+
reloaded_obj.read_attachment(@attachment_name).should == file.read
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'should se the content-type if passed' do
|
|
100
|
+
file = File.open(FIXTURE_PATH + '/attachments/README')
|
|
101
|
+
@file.should_not == file
|
|
102
|
+
@obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
|
|
103
|
+
@obj['_attachments'][@attachment_name]['content-type'].should == @content_type
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'should delete an attachment that exists' do
|
|
107
|
+
@obj.delete_attachment(@attachment_name)
|
|
108
|
+
@obj.save
|
|
109
|
+
lambda{Basic.get(@obj.id).read_attachment(@attachment_name)}.should raise_error
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe "#attachment_url" do
|
|
114
|
+
before(:each) do
|
|
115
|
+
@obj = Basic.new
|
|
116
|
+
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
|
117
|
+
@attachment_name = 'my_attachment'
|
|
118
|
+
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
|
119
|
+
@obj.save.should == true
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'should return nil if attachment does not exist' do
|
|
123
|
+
@obj.attachment_url('bogus').should be_nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'should return the attachment URL as specified by CouchDB HttpDocumentApi' do
|
|
127
|
+
@obj.attachment_url(@attachment_name).should == "#{Basic.database}/#{@obj.id}/#{@attachment_name}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'should return the attachment URI' do
|
|
131
|
+
@obj.attachment_uri(@attachment_name).should == "#{Basic.database.uri}/#{@obj.id}/#{@attachment_name}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
require File.join(FIXTURE_PATH, 'more', 'article')
|
|
3
|
+
require File.join(FIXTURE_PATH, 'more', 'course')
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
describe "ExtendedDocument" do
|
|
7
|
+
|
|
8
|
+
class WithDefaultValues < CouchRest::ExtendedDocument
|
|
9
|
+
use_database TEST_SERVER.default_database
|
|
10
|
+
property :preset, :default => {:right => 10, :top_align => false}
|
|
11
|
+
property :set_by_proc, :default => Proc.new{Time.now}, :cast_as => 'Time'
|
|
12
|
+
property :tags, :default => []
|
|
13
|
+
property :read_only_with_default, :default => 'generic', :read_only => true
|
|
14
|
+
property :default_false, :default => false
|
|
15
|
+
property :name
|
|
16
|
+
timestamps!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class WithCallBacks < CouchRest::ExtendedDocument
|
|
20
|
+
use_database TEST_SERVER.default_database
|
|
21
|
+
property :name
|
|
22
|
+
property :run_before_save
|
|
23
|
+
property :run_after_save
|
|
24
|
+
property :run_before_create
|
|
25
|
+
property :run_after_create
|
|
26
|
+
property :run_before_update
|
|
27
|
+
property :run_after_update
|
|
28
|
+
|
|
29
|
+
save_callback :before do |object|
|
|
30
|
+
object.run_before_save = true
|
|
31
|
+
end
|
|
32
|
+
save_callback :after do |object|
|
|
33
|
+
object.run_after_save = true
|
|
34
|
+
end
|
|
35
|
+
create_callback :before do |object|
|
|
36
|
+
object.run_before_create = true
|
|
37
|
+
end
|
|
38
|
+
create_callback :after do |object|
|
|
39
|
+
object.run_after_create = true
|
|
40
|
+
end
|
|
41
|
+
update_callback :before do |object|
|
|
42
|
+
object.run_before_update = true
|
|
43
|
+
end
|
|
44
|
+
update_callback :after do |object|
|
|
45
|
+
object.run_after_update = true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class WithTemplateAndUniqueID < CouchRest::ExtendedDocument
|
|
50
|
+
use_database TEST_SERVER.default_database
|
|
51
|
+
unique_id do |model|
|
|
52
|
+
model['important-field']
|
|
53
|
+
end
|
|
54
|
+
property :preset, :default => 'value'
|
|
55
|
+
property :has_no_default
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class WithGetterAndSetterMethods < CouchRest::ExtendedDocument
|
|
59
|
+
use_database TEST_SERVER.default_database
|
|
60
|
+
|
|
61
|
+
property :other_arg
|
|
62
|
+
def arg
|
|
63
|
+
other_arg
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def arg=(value)
|
|
67
|
+
self.other_arg = "foo-#{value}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
before(:each) do
|
|
72
|
+
@obj = WithDefaultValues.new
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe "instance database connection" do
|
|
76
|
+
it "should use the default database" do
|
|
77
|
+
@obj.database.name.should == 'couchrest-test'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "should override the default db" do
|
|
81
|
+
@obj.database = TEST_SERVER.database!('couchrest-extendedmodel-test')
|
|
82
|
+
@obj.database.name.should == 'couchrest-extendedmodel-test'
|
|
83
|
+
@obj.database.delete!
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "a new model" do
|
|
88
|
+
it "should be a new_record" do
|
|
89
|
+
@obj = Basic.new
|
|
90
|
+
@obj.rev.should be_nil
|
|
91
|
+
@obj.should be_a_new_record
|
|
92
|
+
end
|
|
93
|
+
it "should be a new_document" do
|
|
94
|
+
@obj = Basic.new
|
|
95
|
+
@obj.rev.should be_nil
|
|
96
|
+
@obj.should be_a_new_document
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "creating a new document" do
|
|
101
|
+
it "should instantialize and save a document" do
|
|
102
|
+
article = Article.create(:title => 'my test')
|
|
103
|
+
article.title.should == 'my test'
|
|
104
|
+
article.should_not be_new_document
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "should trigger the create callbacks" do
|
|
108
|
+
doc = WithCallBacks.create(:name => 'my other test')
|
|
109
|
+
doc.run_before_create.should be_true
|
|
110
|
+
doc.run_after_create.should be_true
|
|
111
|
+
doc.run_before_save.should be_true
|
|
112
|
+
doc.run_after_save.should be_true
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe "update attributes without saving" do
|
|
117
|
+
before(:each) do
|
|
118
|
+
a = Article.get "big-bad-danger" rescue nil
|
|
119
|
+
a.destroy if a
|
|
120
|
+
@art = Article.new(:title => "big bad danger")
|
|
121
|
+
@art.save
|
|
122
|
+
end
|
|
123
|
+
it "should work for attribute= methods" do
|
|
124
|
+
@art['title'].should == "big bad danger"
|
|
125
|
+
@art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
|
|
126
|
+
@art['title'].should == "super danger"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "should flip out if an attribute= method is missing" do
|
|
130
|
+
lambda {
|
|
131
|
+
@art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
|
132
|
+
}.should raise_error
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "should not change other attributes if there is an error" do
|
|
136
|
+
lambda {
|
|
137
|
+
@art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
|
138
|
+
}.should raise_error
|
|
139
|
+
@art['title'].should == "big bad danger"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
describe "update attributes" do
|
|
144
|
+
before(:each) do
|
|
145
|
+
a = Article.get "big-bad-danger" rescue nil
|
|
146
|
+
a.destroy if a
|
|
147
|
+
@art = Article.new(:title => "big bad danger")
|
|
148
|
+
@art.save
|
|
149
|
+
end
|
|
150
|
+
it "should save" do
|
|
151
|
+
@art['title'].should == "big bad danger"
|
|
152
|
+
@art.update_attributes('date' => Time.now, :title => "super danger")
|
|
153
|
+
loaded = Article.get(@art.id)
|
|
154
|
+
loaded['title'].should == "super danger"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
describe "with default" do
|
|
159
|
+
it "should have the default value set at initalization" do
|
|
160
|
+
@obj.preset.should == {:right => 10, :top_align => false}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it "should have the default false value explicitly assigned" do
|
|
164
|
+
@obj.default_false.should == false
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "should automatically call a proc default at initialization" do
|
|
168
|
+
@obj.set_by_proc.should be_an_instance_of(Time)
|
|
169
|
+
@obj.set_by_proc.should == @obj.set_by_proc
|
|
170
|
+
@obj.set_by_proc.should < Time.now
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "should let you overwrite the default values" do
|
|
174
|
+
obj = WithDefaultValues.new(:preset => 'test')
|
|
175
|
+
obj.preset = 'test'
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "should work with a default empty array" do
|
|
179
|
+
obj = WithDefaultValues.new(:tags => ['spec'])
|
|
180
|
+
obj.tags.should == ['spec']
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it "should set default value of read-only property" do
|
|
184
|
+
obj = WithDefaultValues.new
|
|
185
|
+
obj.read_only_with_default.should == 'generic'
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
describe "a doc with template values (CR::Model spec)" do
|
|
190
|
+
before(:all) do
|
|
191
|
+
WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
|
|
192
|
+
WithTemplateAndUniqueID.database.bulk_delete
|
|
193
|
+
@tmpl = WithTemplateAndUniqueID.new
|
|
194
|
+
@tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1')
|
|
195
|
+
end
|
|
196
|
+
it "should have fields set when new" do
|
|
197
|
+
@tmpl.preset.should == 'value'
|
|
198
|
+
end
|
|
199
|
+
it "shouldn't override explicitly set values" do
|
|
200
|
+
@tmpl2.preset.should == 'not_value'
|
|
201
|
+
end
|
|
202
|
+
it "shouldn't override existing documents" do
|
|
203
|
+
@tmpl2.save
|
|
204
|
+
tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
|
|
205
|
+
@tmpl2.preset.should == 'not_value'
|
|
206
|
+
tmpl2_reloaded.preset.should == 'not_value'
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
describe "getting a model" do
|
|
211
|
+
before(:all) do
|
|
212
|
+
@art = Article.new(:title => 'All About Getting')
|
|
213
|
+
@art.save
|
|
214
|
+
end
|
|
215
|
+
it "should load and instantiate it" do
|
|
216
|
+
foundart = Article.get @art.id
|
|
217
|
+
foundart.title.should == "All About Getting"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it "should return nil if `get` is used and the document doesn't exist" do
|
|
221
|
+
foundart = Article.get 'matt aimonetti'
|
|
222
|
+
foundart.should be_nil
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it "should raise an error if `get!` is used and the document doesn't exist" do
|
|
226
|
+
lambda{foundart = Article.get!('matt aimonetti')}.should raise_error
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
describe "getting a model with a subobjects array" do
|
|
231
|
+
before(:all) do
|
|
232
|
+
course_doc = {
|
|
233
|
+
"title" => "Metaphysics 200",
|
|
234
|
+
"questions" => [
|
|
235
|
+
{
|
|
236
|
+
"q" => "Carve the ___ of reality at the ___.",
|
|
237
|
+
"a" => ["beast","joints"]
|
|
238
|
+
},{
|
|
239
|
+
"q" => "Who layed the smack down on Leibniz's Law?",
|
|
240
|
+
"a" => "Willard Van Orman Quine"
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
r = Course.database.save_doc course_doc
|
|
245
|
+
@course = Course.get r['id']
|
|
246
|
+
end
|
|
247
|
+
it "should load the course" do
|
|
248
|
+
@course.title.should == "Metaphysics 200"
|
|
249
|
+
end
|
|
250
|
+
it "should instantiate them as such" do
|
|
251
|
+
@course["questions"][0].a[0].should == "beast"
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
describe "finding all instances of a model" do
|
|
256
|
+
before(:all) do
|
|
257
|
+
WithTemplateAndUniqueID.design_doc_fresh = false
|
|
258
|
+
WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
|
|
259
|
+
WithTemplateAndUniqueID.database.bulk_delete
|
|
260
|
+
WithTemplateAndUniqueID.new('important-field' => '1').save
|
|
261
|
+
WithTemplateAndUniqueID.new('important-field' => '2').save
|
|
262
|
+
WithTemplateAndUniqueID.new('important-field' => '3').save
|
|
263
|
+
WithTemplateAndUniqueID.new('important-field' => '4').save
|
|
264
|
+
end
|
|
265
|
+
it "should make the design doc" do
|
|
266
|
+
WithTemplateAndUniqueID.all
|
|
267
|
+
d = WithTemplateAndUniqueID.design_doc
|
|
268
|
+
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
|
|
269
|
+
end
|
|
270
|
+
it "should find all" do
|
|
271
|
+
rs = WithTemplateAndUniqueID.all
|
|
272
|
+
rs.length.should == 4
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
describe "counting all instances of a model" do
|
|
277
|
+
before(:each) do
|
|
278
|
+
@db = reset_test_db!
|
|
279
|
+
WithTemplateAndUniqueID.design_doc_fresh = false
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it ".count should return 0 if there are no docuemtns" do
|
|
283
|
+
WithTemplateAndUniqueID.count.should == 0
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
it ".count should return the number of documents" do
|
|
287
|
+
WithTemplateAndUniqueID.new('important-field' => '1').save
|
|
288
|
+
WithTemplateAndUniqueID.new('important-field' => '2').save
|
|
289
|
+
WithTemplateAndUniqueID.new('important-field' => '3').save
|
|
290
|
+
|
|
291
|
+
WithTemplateAndUniqueID.count.should == 3
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
describe "finding the first instance of a model" do
|
|
296
|
+
before(:each) do
|
|
297
|
+
@db = reset_test_db!
|
|
298
|
+
WithTemplateAndUniqueID.design_doc_fresh = false
|
|
299
|
+
WithTemplateAndUniqueID.new('important-field' => '1').save
|
|
300
|
+
WithTemplateAndUniqueID.new('important-field' => '2').save
|
|
301
|
+
WithTemplateAndUniqueID.new('important-field' => '3').save
|
|
302
|
+
WithTemplateAndUniqueID.new('important-field' => '4').save
|
|
303
|
+
end
|
|
304
|
+
it "should make the design doc" do
|
|
305
|
+
WithTemplateAndUniqueID.all
|
|
306
|
+
d = WithTemplateAndUniqueID.design_doc
|
|
307
|
+
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
|
|
308
|
+
end
|
|
309
|
+
it "should find first" do
|
|
310
|
+
rs = WithTemplateAndUniqueID.first
|
|
311
|
+
rs['important-field'].should == "1"
|
|
312
|
+
end
|
|
313
|
+
it "should return nil if no instances are found" do
|
|
314
|
+
WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
|
|
315
|
+
WithTemplateAndUniqueID.first.should be_nil
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
describe "getting a model with a subobject field" do
|
|
320
|
+
before(:all) do
|
|
321
|
+
course_doc = {
|
|
322
|
+
"title" => "Metaphysics 410",
|
|
323
|
+
"professor" => {
|
|
324
|
+
"name" => ["Mark", "Hinchliff"]
|
|
325
|
+
},
|
|
326
|
+
"final_test_at" => "2008/12/19 13:00:00 +0800"
|
|
327
|
+
}
|
|
328
|
+
r = Course.database.save_doc course_doc
|
|
329
|
+
@course = Course.get r['id']
|
|
330
|
+
end
|
|
331
|
+
it "should load the course" do
|
|
332
|
+
@course["professor"]["name"][1].should == "Hinchliff"
|
|
333
|
+
end
|
|
334
|
+
it "should instantiate the professor as a person" do
|
|
335
|
+
@course['professor'].last_name.should == "Hinchliff"
|
|
336
|
+
end
|
|
337
|
+
it "should instantiate the final_test_at as a Time" do
|
|
338
|
+
@course['final_test_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
describe "timestamping" do
|
|
343
|
+
before(:each) do
|
|
344
|
+
oldart = Article.get "saving-this" rescue nil
|
|
345
|
+
oldart.destroy if oldart
|
|
346
|
+
@art = Article.new(:title => "Saving this")
|
|
347
|
+
@art.save
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it "should define the updated_at and created_at getters and set the values" do
|
|
351
|
+
@obj.save
|
|
352
|
+
obj = WithDefaultValues.get(@obj.id)
|
|
353
|
+
obj.should be_an_instance_of(WithDefaultValues)
|
|
354
|
+
obj.created_at.should be_an_instance_of(Time)
|
|
355
|
+
obj.updated_at.should be_an_instance_of(Time)
|
|
356
|
+
obj.created_at.to_s.should == @obj.updated_at.to_s
|
|
357
|
+
end
|
|
358
|
+
it "should set the time on create" do
|
|
359
|
+
(Time.now - @art.created_at).should < 2
|
|
360
|
+
foundart = Article.get @art.id
|
|
361
|
+
foundart.created_at.should == foundart.updated_at
|
|
362
|
+
end
|
|
363
|
+
it "should set the time on update" do
|
|
364
|
+
@art.save
|
|
365
|
+
@art.created_at.should < @art.updated_at
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
describe "basic saving and retrieving" do
|
|
370
|
+
it "should work fine" do
|
|
371
|
+
@obj.name = "should be easily saved and retrieved"
|
|
372
|
+
@obj.save
|
|
373
|
+
saved_obj = WithDefaultValues.get(@obj.id)
|
|
374
|
+
saved_obj.should_not be_nil
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
it "should parse the Time attributes automatically" do
|
|
378
|
+
@obj.name = "should parse the Time attributes automatically"
|
|
379
|
+
@obj.set_by_proc.should be_an_instance_of(Time)
|
|
380
|
+
@obj.save
|
|
381
|
+
@obj.set_by_proc.should be_an_instance_of(Time)
|
|
382
|
+
saved_obj = WithDefaultValues.get(@obj.id)
|
|
383
|
+
saved_obj.set_by_proc.should be_an_instance_of(Time)
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
describe "saving a model" do
|
|
388
|
+
before(:all) do
|
|
389
|
+
@sobj = Basic.new
|
|
390
|
+
@sobj.save.should == true
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
it "should save the doc" do
|
|
394
|
+
doc = Basic.get(@sobj.id)
|
|
395
|
+
doc['_id'].should == @sobj.id
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
it "should be set for resaving" do
|
|
399
|
+
rev = @obj.rev
|
|
400
|
+
@sobj['another-key'] = "some value"
|
|
401
|
+
@sobj.save
|
|
402
|
+
@sobj.rev.should_not == rev
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
it "should set the id" do
|
|
406
|
+
@sobj.id.should be_an_instance_of(String)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
it "should set the type" do
|
|
410
|
+
@sobj['couchrest-type'].should == 'Basic'
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
describe "saving a model with a unique_id configured" do
|
|
415
|
+
before(:each) do
|
|
416
|
+
@art = Article.new
|
|
417
|
+
@old = Article.database.get('this-is-the-title') rescue nil
|
|
418
|
+
Article.database.delete_doc(@old) if @old
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
it "should be a new document" do
|
|
422
|
+
@art.should be_a_new_document
|
|
423
|
+
@art.title.should be_nil
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
it "should require the title" do
|
|
427
|
+
lambda{@art.save}.should raise_error
|
|
428
|
+
@art.title = 'This is the title'
|
|
429
|
+
@art.save.should == true
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
it "should not change the slug on update" do
|
|
433
|
+
@art.title = 'This is the title'
|
|
434
|
+
@art.save.should == true
|
|
435
|
+
@art.title = 'new title'
|
|
436
|
+
@art.save.should == true
|
|
437
|
+
@art.slug.should == 'this-is-the-title'
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
it "should raise an error when the slug is taken" do
|
|
441
|
+
@art.title = 'This is the title'
|
|
442
|
+
@art.save.should == true
|
|
443
|
+
@art2 = Article.new(:title => 'This is the title!')
|
|
444
|
+
lambda{@art2.save}.should raise_error
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
it "should set the slug" do
|
|
448
|
+
@art.title = 'This is the title'
|
|
449
|
+
@art.save.should == true
|
|
450
|
+
@art.slug.should == 'this-is-the-title'
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
it "should set the id" do
|
|
454
|
+
@art.title = 'This is the title'
|
|
455
|
+
@art.save.should == true
|
|
456
|
+
@art.id.should == 'this-is-the-title'
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
describe "saving a model with a unique_id lambda" do
|
|
461
|
+
before(:each) do
|
|
462
|
+
@templated = WithTemplateAndUniqueID.new
|
|
463
|
+
@old = WithTemplateAndUniqueID.get('very-important') rescue nil
|
|
464
|
+
@old.destroy if @old
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
it "should require the field" do
|
|
468
|
+
lambda{@templated.save}.should raise_error
|
|
469
|
+
@templated['important-field'] = 'very-important'
|
|
470
|
+
@templated.save.should == true
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
it "should save with the id" do
|
|
474
|
+
@templated['important-field'] = 'very-important'
|
|
475
|
+
@templated.save.should == true
|
|
476
|
+
t = WithTemplateAndUniqueID.get('very-important')
|
|
477
|
+
t.should == @templated
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
it "should not change the id on update" do
|
|
481
|
+
@templated['important-field'] = 'very-important'
|
|
482
|
+
@templated.save.should == true
|
|
483
|
+
@templated['important-field'] = 'not-important'
|
|
484
|
+
@templated.save.should == true
|
|
485
|
+
t = WithTemplateAndUniqueID.get('very-important')
|
|
486
|
+
t.should == @templated
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
it "should raise an error when the id is taken" do
|
|
490
|
+
@templated['important-field'] = 'very-important'
|
|
491
|
+
@templated.save.should == true
|
|
492
|
+
lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
it "should set the id" do
|
|
496
|
+
@templated['important-field'] = 'very-important'
|
|
497
|
+
@templated.save.should == true
|
|
498
|
+
@templated.id.should == 'very-important'
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
describe "destroying an instance" do
|
|
503
|
+
before(:each) do
|
|
504
|
+
@dobj = Basic.new
|
|
505
|
+
@dobj.save.should == true
|
|
506
|
+
end
|
|
507
|
+
it "should return true" do
|
|
508
|
+
result = @dobj.destroy
|
|
509
|
+
result.should == true
|
|
510
|
+
end
|
|
511
|
+
it "should be resavable" do
|
|
512
|
+
@dobj.destroy
|
|
513
|
+
@dobj.rev.should be_nil
|
|
514
|
+
@dobj.id.should be_nil
|
|
515
|
+
@dobj.save.should == true
|
|
516
|
+
end
|
|
517
|
+
it "should make it go away" do
|
|
518
|
+
@dobj.destroy
|
|
519
|
+
lambda{Basic.get!(@dobj.id)}.should raise_error
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
describe "callbacks" do
|
|
525
|
+
|
|
526
|
+
before(:each) do
|
|
527
|
+
@doc = WithCallBacks.new
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
describe "save" do
|
|
531
|
+
it "should run the after filter after saving" do
|
|
532
|
+
@doc.run_after_save.should be_nil
|
|
533
|
+
@doc.save.should be_true
|
|
534
|
+
@doc.run_after_save.should be_true
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
describe "create" do
|
|
538
|
+
it "should run the before save filter when creating" do
|
|
539
|
+
@doc.run_before_save.should be_nil
|
|
540
|
+
@doc.create.should_not be_nil
|
|
541
|
+
@doc.run_before_save.should be_true
|
|
542
|
+
end
|
|
543
|
+
it "should run the before create filter" do
|
|
544
|
+
@doc.run_before_create.should be_nil
|
|
545
|
+
@doc.create.should_not be_nil
|
|
546
|
+
@doc.create
|
|
547
|
+
@doc.run_before_create.should be_true
|
|
548
|
+
end
|
|
549
|
+
it "should run the after create filter" do
|
|
550
|
+
@doc.run_after_create.should be_nil
|
|
551
|
+
@doc.create.should_not be_nil
|
|
552
|
+
@doc.create
|
|
553
|
+
@doc.run_after_create.should be_true
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
describe "update" do
|
|
557
|
+
|
|
558
|
+
before(:each) do
|
|
559
|
+
@doc.save
|
|
560
|
+
end
|
|
561
|
+
it "should run the before update filter when updating an existing document" do
|
|
562
|
+
@doc.run_before_update.should be_nil
|
|
563
|
+
@doc.update
|
|
564
|
+
@doc.run_before_update.should be_true
|
|
565
|
+
end
|
|
566
|
+
it "should run the after update filter when updating an existing document" do
|
|
567
|
+
@doc.run_after_update.should be_nil
|
|
568
|
+
@doc.update
|
|
569
|
+
@doc.run_after_update.should be_true
|
|
570
|
+
end
|
|
571
|
+
it "should run the before update filter when saving an existing document" do
|
|
572
|
+
@doc.run_before_update.should be_nil
|
|
573
|
+
@doc.save
|
|
574
|
+
@doc.run_before_update.should be_true
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
describe "getter and setter methods" do
|
|
581
|
+
it "should try to call the arg= method before setting :arg in the hash" do
|
|
582
|
+
@doc = WithGetterAndSetterMethods.new(:arg => "foo")
|
|
583
|
+
@doc['arg'].should be_nil
|
|
584
|
+
@doc[:arg].should be_nil
|
|
585
|
+
@doc.other_arg.should == "foo-foo"
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
end
|