openlogic-couchrest_model 1.0.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 +11 -0
- data/.rspec +4 -0
- data/Gemfile +4 -0
- data/LICENSE +176 -0
- data/README.md +137 -0
- data/Rakefile +38 -0
- data/THANKS.md +21 -0
- data/VERSION +1 -0
- data/benchmarks/dirty.rb +118 -0
- data/couchrest_model.gemspec +36 -0
- data/history.md +309 -0
- data/init.rb +1 -0
- data/lib/couchrest/model.rb +10 -0
- data/lib/couchrest/model/associations.rb +231 -0
- data/lib/couchrest/model/base.rb +129 -0
- data/lib/couchrest/model/callbacks.rb +28 -0
- data/lib/couchrest/model/casted_array.rb +83 -0
- data/lib/couchrest/model/casted_by.rb +33 -0
- data/lib/couchrest/model/casted_hash.rb +84 -0
- data/lib/couchrest/model/class_proxy.rb +135 -0
- data/lib/couchrest/model/collection.rb +273 -0
- data/lib/couchrest/model/configuration.rb +67 -0
- data/lib/couchrest/model/connection.rb +70 -0
- data/lib/couchrest/model/core_extensions/hash.rb +9 -0
- data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
- data/lib/couchrest/model/design_doc.rb +128 -0
- data/lib/couchrest/model/designs.rb +91 -0
- data/lib/couchrest/model/designs/view.rb +513 -0
- data/lib/couchrest/model/dirty.rb +39 -0
- data/lib/couchrest/model/document_queries.rb +99 -0
- data/lib/couchrest/model/embeddable.rb +78 -0
- data/lib/couchrest/model/errors.rb +25 -0
- data/lib/couchrest/model/extended_attachments.rb +83 -0
- data/lib/couchrest/model/persistence.rb +178 -0
- data/lib/couchrest/model/properties.rb +228 -0
- data/lib/couchrest/model/property.rb +114 -0
- data/lib/couchrest/model/property_protection.rb +71 -0
- data/lib/couchrest/model/proxyable.rb +183 -0
- data/lib/couchrest/model/support/couchrest_database.rb +13 -0
- data/lib/couchrest/model/support/couchrest_design.rb +33 -0
- data/lib/couchrest/model/typecast.rb +154 -0
- data/lib/couchrest/model/validations.rb +80 -0
- data/lib/couchrest/model/validations/casted_model.rb +16 -0
- data/lib/couchrest/model/validations/locale/en.yml +5 -0
- data/lib/couchrest/model/validations/uniqueness.rb +69 -0
- data/lib/couchrest/model/views.rb +151 -0
- data/lib/couchrest/railtie.rb +24 -0
- data/lib/couchrest_model.rb +66 -0
- data/lib/rails/generators/couchrest_model.rb +16 -0
- data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
- data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
- data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
- data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
- data/spec/.gitignore +1 -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/config/couchdb.yml +10 -0
- data/spec/fixtures/models/article.rb +36 -0
- data/spec/fixtures/models/base.rb +164 -0
- data/spec/fixtures/models/card.rb +19 -0
- data/spec/fixtures/models/cat.rb +23 -0
- data/spec/fixtures/models/client.rb +6 -0
- data/spec/fixtures/models/course.rb +27 -0
- data/spec/fixtures/models/event.rb +8 -0
- data/spec/fixtures/models/invoice.rb +14 -0
- data/spec/fixtures/models/key_chain.rb +5 -0
- data/spec/fixtures/models/membership.rb +4 -0
- data/spec/fixtures/models/person.rb +11 -0
- data/spec/fixtures/models/project.rb +6 -0
- data/spec/fixtures/models/question.rb +7 -0
- data/spec/fixtures/models/sale_entry.rb +9 -0
- data/spec/fixtures/models/sale_invoice.rb +14 -0
- data/spec/fixtures/models/service.rb +10 -0
- data/spec/fixtures/models/user.rb +22 -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/functional/validations_spec.rb +8 -0
- data/spec/spec_helper.rb +60 -0
- data/spec/unit/active_model_lint_spec.rb +30 -0
- data/spec/unit/assocations_spec.rb +242 -0
- data/spec/unit/attachment_spec.rb +176 -0
- data/spec/unit/base_spec.rb +537 -0
- data/spec/unit/casted_spec.rb +72 -0
- data/spec/unit/class_proxy_spec.rb +167 -0
- data/spec/unit/collection_spec.rb +86 -0
- data/spec/unit/configuration_spec.rb +77 -0
- data/spec/unit/connection_spec.rb +148 -0
- data/spec/unit/core_extensions/time_parsing.rb +77 -0
- data/spec/unit/design_doc_spec.rb +241 -0
- data/spec/unit/designs/view_spec.rb +831 -0
- data/spec/unit/designs_spec.rb +134 -0
- data/spec/unit/dirty_spec.rb +436 -0
- data/spec/unit/embeddable_spec.rb +498 -0
- data/spec/unit/inherited_spec.rb +33 -0
- data/spec/unit/persistence_spec.rb +481 -0
- data/spec/unit/property_protection_spec.rb +192 -0
- data/spec/unit/property_spec.rb +481 -0
- data/spec/unit/proxyable_spec.rb +376 -0
- data/spec/unit/subclass_spec.rb +85 -0
- data/spec/unit/typecast_spec.rb +521 -0
- data/spec/unit/validations_spec.rb +140 -0
- data/spec/unit/view_spec.rb +367 -0
- metadata +301 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe "Model attachments" do
|
|
4
|
+
|
|
5
|
+
describe "#has_attachment?" do
|
|
6
|
+
before(:each) do
|
|
7
|
+
reset_test_db!
|
|
8
|
+
@obj = Basic.new
|
|
9
|
+
@obj.save.should be_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
|
+
|
|
34
|
+
it 'should return false if an attachment has been removed and reloaded' do
|
|
35
|
+
@obj.delete_attachment(@attachment_name)
|
|
36
|
+
reloaded_obj = Basic.get(@obj.id)
|
|
37
|
+
reloaded_obj.has_attachment?(@attachment_name).should be_false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "creating an attachment" do
|
|
43
|
+
before(:each) do
|
|
44
|
+
@obj = Basic.new
|
|
45
|
+
@obj.save.should be_true
|
|
46
|
+
@file_ext = File.open(FIXTURE_PATH + '/attachments/test.html')
|
|
47
|
+
@file_no_ext = File.open(FIXTURE_PATH + '/attachments/README')
|
|
48
|
+
@attachment_name = 'my_attachment'
|
|
49
|
+
@content_type = 'media/mp3'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should create an attachment from file with an extension" do
|
|
53
|
+
@obj.create_attachment(:file => @file_ext, :name => @attachment_name)
|
|
54
|
+
@obj.save.should be_true
|
|
55
|
+
reloaded_obj = Basic.get(@obj.id)
|
|
56
|
+
reloaded_obj.attachments[@attachment_name].should_not be_nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "should create an attachment from file without an extension" do
|
|
60
|
+
@obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
|
|
61
|
+
@obj.save.should be_true
|
|
62
|
+
reloaded_obj = Basic.get(@obj.id)
|
|
63
|
+
reloaded_obj.attachments[@attachment_name].should_not be_nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'should raise ArgumentError if :file is missing' do
|
|
67
|
+
lambda{ @obj.create_attachment(:name => @attachment_name) }.should raise_error
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'should raise ArgumentError if :name is missing' do
|
|
71
|
+
lambda{ @obj.create_attachment(:file => @file_ext) }.should raise_error
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'should set the content-type if passed' do
|
|
75
|
+
@obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
|
|
76
|
+
@obj.attachments[@attachment_name]['content_type'].should == @content_type
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "should detect the content-type automatically" do
|
|
80
|
+
@obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
|
|
81
|
+
@obj.attachments['couchdb.png']['content_type'].should == "image/png"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "should use name to detect the content-type automatically if no file" do
|
|
85
|
+
file = File.open(FIXTURE_PATH + '/attachments/couchdb.png')
|
|
86
|
+
file.stub!(:path).and_return("badfilname")
|
|
87
|
+
@obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
|
|
88
|
+
@obj.attachments['couchdb.png']['content_type'].should == "image/png"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe 'reading, updating, and deleting an attachment' do
|
|
94
|
+
before(:each) do
|
|
95
|
+
@obj = Basic.new
|
|
96
|
+
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
|
97
|
+
@attachment_name = 'my_attachment'
|
|
98
|
+
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
|
99
|
+
@obj.save.should be_true
|
|
100
|
+
@file.rewind
|
|
101
|
+
@content_type = 'media/mp3'
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'should read an attachment that exists' do
|
|
105
|
+
@obj.read_attachment(@attachment_name).should == @file.read
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'should update an attachment that exists' do
|
|
109
|
+
file = File.open(FIXTURE_PATH + '/attachments/README')
|
|
110
|
+
@file.should_not == file
|
|
111
|
+
@obj.update_attachment(:file => file, :name => @attachment_name)
|
|
112
|
+
@obj.save
|
|
113
|
+
reloaded_obj = Basic.get(@obj.id)
|
|
114
|
+
file.rewind
|
|
115
|
+
reloaded_obj.read_attachment(@attachment_name).should_not == @file.read
|
|
116
|
+
reloaded_obj.read_attachment(@attachment_name).should == file.read
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'should set the content-type if passed' do
|
|
120
|
+
file = File.open(FIXTURE_PATH + '/attachments/README')
|
|
121
|
+
@file.should_not == file
|
|
122
|
+
@obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
|
|
123
|
+
@obj.attachments[@attachment_name]['content_type'].should == @content_type
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'should delete an attachment that exists' do
|
|
127
|
+
@obj.delete_attachment(@attachment_name)
|
|
128
|
+
@obj.save
|
|
129
|
+
lambda{Basic.get(@obj.id).read_attachment(@attachment_name)}.should raise_error
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe "#attachment_url" do
|
|
134
|
+
before(:each) do
|
|
135
|
+
@obj = Basic.new
|
|
136
|
+
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
|
137
|
+
@attachment_name = 'my_attachment'
|
|
138
|
+
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
|
139
|
+
@obj.save.should be_true
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'should return nil if attachment does not exist' do
|
|
143
|
+
@obj.attachment_url('bogus').should be_nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'should return the attachment URL as specified by CouchDB HttpDocumentApi' do
|
|
147
|
+
@obj.attachment_url(@attachment_name).should == "#{Basic.database}/#{@obj.id}/#{@attachment_name}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'should return the attachment URI' do
|
|
151
|
+
@obj.attachment_uri(@attachment_name).should == "#{Basic.database.uri}/#{@obj.id}/#{@attachment_name}"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe "#attachments" do
|
|
156
|
+
before(:each) do
|
|
157
|
+
@obj = Basic.new
|
|
158
|
+
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
|
159
|
+
@attachment_name = 'my_attachment'
|
|
160
|
+
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
|
161
|
+
@obj.save.should be_true
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it 'should return an empty Hash when document does not have any attachment' do
|
|
165
|
+
new_obj = Basic.new
|
|
166
|
+
new_obj.save.should be_true
|
|
167
|
+
new_obj.attachments.should == {}
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'should return a Hash with all attachments' do
|
|
171
|
+
@file.rewind
|
|
172
|
+
@obj.attachments.should == { @attachment_name =>{ "data" => "PCFET0NUWVBFIGh0bWw+CjxodG1sPgogIDxoZWFkPgogICAgPHRpdGxlPlRlc3Q8L3RpdGxlPgogIDwvaGVhZD4KICA8Ym9keT4KICAgIDxwPgogICAgICBUZXN0CiAgICA8L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==", "content_type" => "text/html"}}
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
|
|
4
|
+
describe "Model Base" do
|
|
5
|
+
|
|
6
|
+
before(:each) do
|
|
7
|
+
@obj = WithDefaultValues.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe "instance database connection" do
|
|
11
|
+
it "should use the default database" do
|
|
12
|
+
@obj.database.name.should == 'couchrest-model-test'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "should override the default db" do
|
|
16
|
+
@obj.database = TEST_SERVER.database!('couchrest-extendedmodel-test')
|
|
17
|
+
@obj.database.name.should == 'couchrest-extendedmodel-test'
|
|
18
|
+
@obj.database.delete!
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "a new model" do
|
|
23
|
+
it "should be a new document" do
|
|
24
|
+
@obj = Basic.new
|
|
25
|
+
@obj.rev.should be_nil
|
|
26
|
+
@obj.should be_new
|
|
27
|
+
@obj.should be_new_document
|
|
28
|
+
@obj.should be_new_record
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "should not fail with nil argument" do
|
|
32
|
+
@obj = Basic.new(nil)
|
|
33
|
+
@obj.should_not be_nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "should allow the database to be set" do
|
|
37
|
+
@obj = Basic.new(nil, :database => 'database')
|
|
38
|
+
@obj.database.should eql('database')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "should support initialization block" do
|
|
42
|
+
@obj = Basic.new {|b| b.database = 'database'}
|
|
43
|
+
@obj.database.should eql('database')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "should only set defined properties" do
|
|
47
|
+
@doc = WithDefaultValues.new(:name => 'test', :foo => 'bar')
|
|
48
|
+
@doc['name'].should eql('test')
|
|
49
|
+
@doc['foo'].should be_nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should set all properties with :directly_set_attributes option" do
|
|
53
|
+
@doc = WithDefaultValues.new({:name => 'test', :foo => 'bar'}, :directly_set_attributes => true)
|
|
54
|
+
@doc['name'].should eql('test')
|
|
55
|
+
@doc['foo'].should eql('bar')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "should set the model type" do
|
|
59
|
+
@doc = WithDefaultValues.new()
|
|
60
|
+
@doc[WithDefaultValues.model_type_key].should eql('WithDefaultValues')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "should call after_initialize method if available" do
|
|
64
|
+
@doc = WithAfterInitializeMethod.new
|
|
65
|
+
@doc['some_value'].should eql('value')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "should call after_initialize after block" do
|
|
69
|
+
@doc = WithAfterInitializeMethod.new {|d| d.some_value = "foo"}
|
|
70
|
+
@doc['some_value'].should eql('foo')
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "should call after_initialize callback if available" do
|
|
74
|
+
klass = Class.new(CouchRest::Model::Base)
|
|
75
|
+
klass.class_eval do # for ruby 1.8.7
|
|
76
|
+
property :name
|
|
77
|
+
after_initialize :set_name
|
|
78
|
+
def set_name; self.name = "foobar"; end
|
|
79
|
+
end
|
|
80
|
+
@doc = klass.new
|
|
81
|
+
@doc.name.should eql("foobar")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe "ActiveModel compatability Basic" do
|
|
86
|
+
|
|
87
|
+
before(:each) do
|
|
88
|
+
@obj = Basic.new(nil)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe "#to_key" do
|
|
92
|
+
context "when the document is new" do
|
|
93
|
+
it "returns nil" do
|
|
94
|
+
@obj.to_key.should be_nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
context "when the document is not new" do
|
|
99
|
+
it "returns id in an array" do
|
|
100
|
+
@obj.save
|
|
101
|
+
@obj.to_key.should eql([@obj['_id']])
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe "#to_param" do
|
|
107
|
+
context "when the document is new" do
|
|
108
|
+
it "returns nil" do
|
|
109
|
+
@obj.to_param.should be_nil
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context "when the document is not new" do
|
|
114
|
+
it "returns id" do
|
|
115
|
+
@obj.save
|
|
116
|
+
@obj.to_param.should eql(@obj['_id'])
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe "#persisted?" do
|
|
122
|
+
context "when the document is new" do
|
|
123
|
+
it "returns false" do
|
|
124
|
+
@obj.persisted?.should be_false
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
context "when the document is not new" do
|
|
129
|
+
it "returns id" do
|
|
130
|
+
@obj.save
|
|
131
|
+
@obj.persisted?.should be_true
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
context "when the document is destroyed" do
|
|
136
|
+
it "returns false" do
|
|
137
|
+
@obj.save
|
|
138
|
+
@obj.destroy
|
|
139
|
+
@obj.persisted?.should be_false
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe "#model_name" do
|
|
145
|
+
it "returns the name of the model" do
|
|
146
|
+
@obj.class.model_name.should eql('Basic')
|
|
147
|
+
WithDefaultValues.model_name.human.should eql("With default values")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "#destroyed?" do
|
|
152
|
+
it "should be present" do
|
|
153
|
+
@obj.should respond_to(:destroyed?)
|
|
154
|
+
end
|
|
155
|
+
it "should return false with new object" do
|
|
156
|
+
@obj.destroyed?.should be_false
|
|
157
|
+
end
|
|
158
|
+
it "should return true after destroy" do
|
|
159
|
+
@obj.save
|
|
160
|
+
@obj.destroy
|
|
161
|
+
@obj.destroyed?.should be_true
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe "comparisons" do
|
|
167
|
+
describe "#==" do
|
|
168
|
+
context "on saved document" do
|
|
169
|
+
it "should be true on same document" do
|
|
170
|
+
p = Project.create
|
|
171
|
+
p.should eql(p)
|
|
172
|
+
end
|
|
173
|
+
it "should be true after loading" do
|
|
174
|
+
p = Project.create
|
|
175
|
+
p.should eql(Project.get(p.id))
|
|
176
|
+
end
|
|
177
|
+
it "should not be true if databases do not match" do
|
|
178
|
+
p = Project.create
|
|
179
|
+
p2 = p.dup
|
|
180
|
+
p2.stub!(:database).and_return('other')
|
|
181
|
+
p.should_not eql(p2)
|
|
182
|
+
end
|
|
183
|
+
it "should always be false if one document not saved" do
|
|
184
|
+
p = Project.create(:name => 'test')
|
|
185
|
+
o = Project.new(:name => 'test')
|
|
186
|
+
p.should_not eql(o)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
context "with new documents" do
|
|
190
|
+
it "should be true when attributes match" do
|
|
191
|
+
p = Project.new(:name => 'test')
|
|
192
|
+
o = Project.new(:name => 'test')
|
|
193
|
+
p.should eql(o)
|
|
194
|
+
end
|
|
195
|
+
it "should not be true when attributes don't match" do
|
|
196
|
+
p = Project.new(:name => 'test')
|
|
197
|
+
o = Project.new(:name => 'testing')
|
|
198
|
+
p.should_not eql(o)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
describe "update attributes without saving" do
|
|
205
|
+
before(:each) do
|
|
206
|
+
a = Article.get "big-bad-danger" rescue nil
|
|
207
|
+
a.destroy if a
|
|
208
|
+
@art = Article.new(:title => "big bad danger")
|
|
209
|
+
@art.save
|
|
210
|
+
end
|
|
211
|
+
it "should work for attribute= methods" do
|
|
212
|
+
@art['title'].should == "big bad danger"
|
|
213
|
+
@art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
|
|
214
|
+
@art['title'].should == "super danger"
|
|
215
|
+
end
|
|
216
|
+
it "should silently ignore _id" do
|
|
217
|
+
@art.update_attributes_without_saving('_id' => 'foobar')
|
|
218
|
+
@art['_id'].should_not == 'foobar'
|
|
219
|
+
end
|
|
220
|
+
it "should silently ignore _rev" do
|
|
221
|
+
@art.update_attributes_without_saving('_rev' => 'foobar')
|
|
222
|
+
@art['_rev'].should_not == 'foobar'
|
|
223
|
+
end
|
|
224
|
+
it "should silently ignore created_at" do
|
|
225
|
+
@art.update_attributes_without_saving('created_at' => 'foobar')
|
|
226
|
+
@art['created_at'].should_not == 'foobar'
|
|
227
|
+
end
|
|
228
|
+
it "should silently ignore updated_at" do
|
|
229
|
+
@art.update_attributes_without_saving('updated_at' => 'foobar')
|
|
230
|
+
@art['updated_at'].should_not == 'foobar'
|
|
231
|
+
end
|
|
232
|
+
it "should also work using attributes= alias" do
|
|
233
|
+
@art.respond_to?(:attributes=).should be_true
|
|
234
|
+
@art.attributes = {'date' => Time.now, :title => "something else"}
|
|
235
|
+
@art['title'].should == "something else"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it "should not flip out if an attribute= method is missing and ignore it" do
|
|
239
|
+
lambda {
|
|
240
|
+
@art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
|
241
|
+
}.should_not raise_error
|
|
242
|
+
@art.slug.should == "big-bad-danger"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
#it "should not change other attributes if there is an error" do
|
|
246
|
+
# lambda {
|
|
247
|
+
# @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
|
248
|
+
# }.should raise_error
|
|
249
|
+
# @art['title'].should == "big bad danger"
|
|
250
|
+
#end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
describe "update attributes" do
|
|
254
|
+
before(:each) do
|
|
255
|
+
a = Article.get "big-bad-danger" rescue nil
|
|
256
|
+
a.destroy if a
|
|
257
|
+
@art = Article.new(:title => "big bad danger")
|
|
258
|
+
@art.save
|
|
259
|
+
end
|
|
260
|
+
it "should save" do
|
|
261
|
+
@art['title'].should == "big bad danger"
|
|
262
|
+
@art.update_attributes('date' => Time.now, :title => "super danger")
|
|
263
|
+
loaded = Article.get(@art.id)
|
|
264
|
+
loaded['title'].should == "super danger"
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
describe "with default" do
|
|
269
|
+
it "should have the default value set at initalization" do
|
|
270
|
+
@obj.preset.should == {:right => 10, :top_align => false}
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
it "should have the default false value explicitly assigned" do
|
|
274
|
+
@obj.default_false.should == false
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
it "should automatically call a proc default at initialization" do
|
|
278
|
+
@obj.set_by_proc.should be_an_instance_of(Time)
|
|
279
|
+
@obj.set_by_proc.should == @obj.set_by_proc
|
|
280
|
+
@obj.set_by_proc.should < Time.now
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it "should let you overwrite the default values" do
|
|
284
|
+
obj = WithDefaultValues.new(:preset => 'test')
|
|
285
|
+
obj.preset = 'test'
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it "should keep default values for new instances" do
|
|
289
|
+
obj = WithDefaultValues.new
|
|
290
|
+
obj.preset[:alpha] = 123
|
|
291
|
+
obj.preset.should == {:right => 10, :top_align => false, :alpha => 123}
|
|
292
|
+
another = WithDefaultValues.new
|
|
293
|
+
another.preset.should == {:right => 10, :top_align => false}
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it "should work with a default empty array" do
|
|
297
|
+
obj = WithDefaultValues.new(:tags => ['spec'])
|
|
298
|
+
obj.tags.should == ['spec']
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
it "should set default value of read-only property" do
|
|
302
|
+
obj = WithDefaultValues.new
|
|
303
|
+
obj.read_only_with_default.should == 'generic'
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
describe "simplified way of setting property types" do
|
|
308
|
+
it "should set defaults" do
|
|
309
|
+
obj = WithSimplePropertyType.new
|
|
310
|
+
obj.preset.should eql('none')
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it "should handle arrays" do
|
|
314
|
+
obj = WithSimplePropertyType.new(:tags => ['spec'])
|
|
315
|
+
obj.tags.should == ['spec']
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
describe "a doc with template values (CR::Model spec)" do
|
|
320
|
+
before(:all) do
|
|
321
|
+
WithTemplateAndUniqueID.all.map{|o| o.destroy}
|
|
322
|
+
WithTemplateAndUniqueID.database.bulk_delete
|
|
323
|
+
@tmpl = WithTemplateAndUniqueID.new
|
|
324
|
+
@tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'slug' => '1')
|
|
325
|
+
end
|
|
326
|
+
it "should have fields set when new" do
|
|
327
|
+
@tmpl.preset.should == 'value'
|
|
328
|
+
end
|
|
329
|
+
it "shouldn't override explicitly set values" do
|
|
330
|
+
@tmpl2.preset.should == 'not_value'
|
|
331
|
+
end
|
|
332
|
+
it "shouldn't override existing documents" do
|
|
333
|
+
@tmpl2.save
|
|
334
|
+
tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
|
|
335
|
+
@tmpl2.preset.should == 'not_value'
|
|
336
|
+
tmpl2_reloaded.preset.should == 'not_value'
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
describe "finding all instances of a model" do
|
|
342
|
+
before(:all) do
|
|
343
|
+
WithTemplateAndUniqueID.all.map{|o| o.destroy}
|
|
344
|
+
WithTemplateAndUniqueID.database.bulk_delete
|
|
345
|
+
WithTemplateAndUniqueID.new('slug' => '1').save
|
|
346
|
+
WithTemplateAndUniqueID.new('slug' => '2').save
|
|
347
|
+
WithTemplateAndUniqueID.new('slug' => '3').save
|
|
348
|
+
WithTemplateAndUniqueID.new('slug' => '4').save
|
|
349
|
+
end
|
|
350
|
+
it "should find all" do
|
|
351
|
+
rs = WithTemplateAndUniqueID.all
|
|
352
|
+
rs.length.should == 4
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
describe "counting all instances of a model" do
|
|
357
|
+
before(:each) do
|
|
358
|
+
@db = reset_test_db!
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
it ".count should return 0 if there are no docuemtns" do
|
|
362
|
+
WithTemplateAndUniqueID.count.should == 0
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
it ".count should return the number of documents" do
|
|
366
|
+
WithTemplateAndUniqueID.new('slug' => '1').save
|
|
367
|
+
WithTemplateAndUniqueID.new('slug' => '2').save
|
|
368
|
+
WithTemplateAndUniqueID.new('slug' => '3').save
|
|
369
|
+
|
|
370
|
+
WithTemplateAndUniqueID.count.should == 3
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
describe "finding the first instance of a model" do
|
|
375
|
+
before(:each) do
|
|
376
|
+
@db = reset_test_db!
|
|
377
|
+
WithTemplateAndUniqueID.new('slug' => '1').save
|
|
378
|
+
WithTemplateAndUniqueID.new('slug' => '2').save
|
|
379
|
+
WithTemplateAndUniqueID.new('slug' => '3').save
|
|
380
|
+
WithTemplateAndUniqueID.new('slug' => '4').save
|
|
381
|
+
end
|
|
382
|
+
it "should find first" do
|
|
383
|
+
rs = WithTemplateAndUniqueID.first
|
|
384
|
+
rs['slug'].should == "1"
|
|
385
|
+
end
|
|
386
|
+
it "should return nil if no instances are found" do
|
|
387
|
+
WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
|
|
388
|
+
WithTemplateAndUniqueID.first.should be_nil
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
describe "getting a model with a subobject field" do
|
|
394
|
+
before(:all) do
|
|
395
|
+
course_doc = {
|
|
396
|
+
"title" => "Metaphysics 410",
|
|
397
|
+
"professor" => {
|
|
398
|
+
"name" => ["Mark", "Hinchliff"]
|
|
399
|
+
},
|
|
400
|
+
"ends_at" => "2008/12/19 13:00:00 +0800"
|
|
401
|
+
}
|
|
402
|
+
r = Course.database.save_doc course_doc
|
|
403
|
+
@course = Course.get r['id']
|
|
404
|
+
end
|
|
405
|
+
it "should load the course" do
|
|
406
|
+
@course["professor"]["name"][1].should == "Hinchliff"
|
|
407
|
+
end
|
|
408
|
+
it "should instantiate the professor as a person" do
|
|
409
|
+
@course['professor'].last_name.should == "Hinchliff"
|
|
410
|
+
end
|
|
411
|
+
it "should instantiate the ends_at as a Time" do
|
|
412
|
+
@course['ends_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
describe "timestamping" do
|
|
417
|
+
before(:each) do
|
|
418
|
+
oldart = Article.get "saving-this" rescue nil
|
|
419
|
+
oldart.destroy if oldart
|
|
420
|
+
@art = Article.new(:title => "Saving this")
|
|
421
|
+
@art.save
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
it "should define the updated_at and created_at getters and set the values" do
|
|
425
|
+
@obj.save
|
|
426
|
+
obj = WithDefaultValues.get(@obj.id)
|
|
427
|
+
obj.should be_an_instance_of(WithDefaultValues)
|
|
428
|
+
obj.created_at.should be_an_instance_of(Time)
|
|
429
|
+
obj.updated_at.should be_an_instance_of(Time)
|
|
430
|
+
obj.created_at.to_s.should == @obj.updated_at.to_s
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
it "should not change created_at on update" do
|
|
434
|
+
2.times do
|
|
435
|
+
lambda do
|
|
436
|
+
@art.save
|
|
437
|
+
end.should_not change(@art, :created_at)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
it "should set the time on create" do
|
|
442
|
+
(Time.now - @art.created_at).should < 2
|
|
443
|
+
foundart = Article.get @art.id
|
|
444
|
+
foundart.created_at.should == foundart.updated_at
|
|
445
|
+
end
|
|
446
|
+
it "should set the time on update" do
|
|
447
|
+
@art.title = "new title" # only saved if @art.changed? == true
|
|
448
|
+
@art.save
|
|
449
|
+
@art.created_at.should < @art.updated_at
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
describe "getter and setter methods" do
|
|
454
|
+
it "should try to call the arg= method before setting :arg in the hash" do
|
|
455
|
+
@doc = WithGetterAndSetterMethods.new(:arg => "foo")
|
|
456
|
+
@doc['arg'].should be_nil
|
|
457
|
+
@doc[:arg].should be_nil
|
|
458
|
+
@doc.other_arg.should == "foo-foo"
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
describe "recursive validation on a model" do
|
|
463
|
+
before :each do
|
|
464
|
+
reset_test_db!
|
|
465
|
+
@cat = Cat.new(:name => 'Sockington')
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
it "should not save if a nested casted model is invalid" do
|
|
469
|
+
@cat.favorite_toy = CatToy.new
|
|
470
|
+
@cat.should_not be_valid
|
|
471
|
+
@cat.save.should be_false
|
|
472
|
+
lambda{@cat.save!}.should raise_error
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
it "should save when nested casted model is valid" do
|
|
476
|
+
@cat.favorite_toy = CatToy.new(:name => 'Squeaky')
|
|
477
|
+
@cat.should be_valid
|
|
478
|
+
@cat.save.should be_true
|
|
479
|
+
lambda{@cat.save!}.should_not raise_error
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
it "should not save when nested collection contains an invalid casted model" do
|
|
483
|
+
@cat.toys = [CatToy.new(:name => 'Feather'), CatToy.new]
|
|
484
|
+
@cat.should_not be_valid
|
|
485
|
+
@cat.save.should be_false
|
|
486
|
+
lambda{@cat.save!}.should raise_error
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
it "should save when nested collection contains valid casted models" do
|
|
490
|
+
@cat.toys = [CatToy.new(:name => 'feather'), CatToy.new(:name => 'ball-o-twine')]
|
|
491
|
+
@cat.should be_valid
|
|
492
|
+
@cat.save.should be_true
|
|
493
|
+
lambda{@cat.save!}.should_not raise_error
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
it "should not fail if the nested casted model doesn't have validation" do
|
|
497
|
+
Cat.property :trainer, Person
|
|
498
|
+
Cat.validates_presence_of :name
|
|
499
|
+
cat = Cat.new(:name => 'Mr Bigglesworth')
|
|
500
|
+
cat.trainer = Person.new
|
|
501
|
+
cat.should be_valid
|
|
502
|
+
cat.save.should be_true
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
describe "searching the contents of a model" do
|
|
507
|
+
before :each do
|
|
508
|
+
@db = reset_test_db!
|
|
509
|
+
|
|
510
|
+
names = ["Fuzzy", "Whiskers", "Mr Bigglesworth", "Sockington", "Smitty", "Sammy", "Samson", "Simon"]
|
|
511
|
+
names.each { |name| Cat.create(:name => name) }
|
|
512
|
+
|
|
513
|
+
search_function = { 'defaults' => {'store' => 'no', 'index' => 'analyzed_no_norms'},
|
|
514
|
+
'index' => "function(doc) { ret = new Document(); ret.add(doc['name'], {'field':'name'}); return ret; }" }
|
|
515
|
+
@db.save_doc({'_id' => '_design/search', 'fulltext' => {'cats' => search_function}})
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
it "should be able to paginate through a large set of search results" do
|
|
519
|
+
if couchdb_lucene_available?
|
|
520
|
+
names = []
|
|
521
|
+
Cat.paginated_each(:design_doc => "search", :view_name => "cats",
|
|
522
|
+
:q => 'name:S*', :search => true, :include_docs => true, :per_page => 3) do |cat|
|
|
523
|
+
cat.should_not be_nil
|
|
524
|
+
names << cat.name
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
names.size.should == 5
|
|
528
|
+
names.should include('Sockington')
|
|
529
|
+
names.should include('Smitty')
|
|
530
|
+
names.should include('Sammy')
|
|
531
|
+
names.should include('Samson')
|
|
532
|
+
names.should include('Simon')
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
end
|