openlogic-couchrest_model 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,192 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Model Attributes" do
|
4
|
+
|
5
|
+
describe "no declarations" do
|
6
|
+
class NoProtection < CouchRest::Model::Base
|
7
|
+
use_database TEST_SERVER.default_database
|
8
|
+
property :name
|
9
|
+
property :phone
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should not protect anything through new" do
|
13
|
+
user = NoProtection.new(:name => "will", :phone => "555-5555")
|
14
|
+
|
15
|
+
user.name.should == "will"
|
16
|
+
user.phone.should == "555-5555"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not protect anything through attributes=" do
|
20
|
+
user = NoProtection.new
|
21
|
+
user.attributes = {:name => "will", :phone => "555-5555"}
|
22
|
+
|
23
|
+
user.name.should == "will"
|
24
|
+
user.phone.should == "555-5555"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should recreate from the database properly" do
|
28
|
+
user = NoProtection.new
|
29
|
+
user.name = "will"
|
30
|
+
user.phone = "555-5555"
|
31
|
+
user.save!
|
32
|
+
|
33
|
+
user = NoProtection.get(user.id)
|
34
|
+
user.name.should == "will"
|
35
|
+
user.phone.should == "555-5555"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should provide a list of all properties as accessible" do
|
39
|
+
user = NoProtection.new(:name => "will", :phone => "555-5555")
|
40
|
+
user.accessible_properties.length.should eql(2)
|
41
|
+
user.protected_properties.should be_empty
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "Model Base", "accessible flag" do
|
46
|
+
class WithAccessible < CouchRest::Model::Base
|
47
|
+
use_database TEST_SERVER.default_database
|
48
|
+
property :name, :accessible => true
|
49
|
+
property :admin, :default => false
|
50
|
+
end
|
51
|
+
|
52
|
+
it { expect { WithAccessible.new(nil) }.to_not raise_error }
|
53
|
+
|
54
|
+
it "should recognize accessible properties" do
|
55
|
+
props = WithAccessible.accessible_properties.map { |prop| prop.name}
|
56
|
+
props.should include("name")
|
57
|
+
props.should_not include("admin")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should protect non-accessible properties set through new" do
|
61
|
+
user = WithAccessible.new(:name => "will", :admin => true)
|
62
|
+
|
63
|
+
user.name.should == "will"
|
64
|
+
user.admin.should == false
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should protect non-accessible properties set through attributes=" do
|
68
|
+
user = WithAccessible.new
|
69
|
+
user.attributes = {:name => "will", :admin => true}
|
70
|
+
|
71
|
+
user.name.should == "will"
|
72
|
+
user.admin.should == false
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should provide correct accessible and protected property lists" do
|
76
|
+
user = WithAccessible.new(:name => 'will', :admin => true)
|
77
|
+
user.accessible_properties.map{|p| p.to_s}.should eql(['name'])
|
78
|
+
user.protected_properties.map{|p| p.to_s}.should eql(['admin'])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "Model Base", "protected flag" do
|
83
|
+
class WithProtected < CouchRest::Model::Base
|
84
|
+
use_database TEST_SERVER.default_database
|
85
|
+
property :name
|
86
|
+
property :admin, :default => false, :protected => true
|
87
|
+
end
|
88
|
+
|
89
|
+
it { expect { WithProtected.new(nil) }.to_not raise_error }
|
90
|
+
|
91
|
+
it "should recognize protected properties" do
|
92
|
+
props = WithProtected.protected_properties.map { |prop| prop.name}
|
93
|
+
props.should_not include("name")
|
94
|
+
props.should include("admin")
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should protect non-accessible properties set through new" do
|
98
|
+
user = WithProtected.new(:name => "will", :admin => true)
|
99
|
+
|
100
|
+
user.name.should == "will"
|
101
|
+
user.admin.should == false
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should protect non-accessible properties set through attributes=" do
|
105
|
+
user = WithProtected.new
|
106
|
+
user.attributes = {:name => "will", :admin => true}
|
107
|
+
|
108
|
+
user.name.should == "will"
|
109
|
+
user.admin.should == false
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should not modify the provided attribute hash" do
|
113
|
+
user = WithProtected.new
|
114
|
+
attrs = {:name => "will", :admin => true}
|
115
|
+
user.attributes = attrs
|
116
|
+
attrs[:admin].should be_true
|
117
|
+
attrs[:name].should eql('will')
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should provide correct accessible and protected property lists" do
|
121
|
+
user = WithProtected.new(:name => 'will', :admin => true)
|
122
|
+
user.accessible_properties.map{|p| p.to_s}.should eql(['name'])
|
123
|
+
user.protected_properties.map{|p| p.to_s}.should eql(['admin'])
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "Model Base", "mixing protected and accessible flags" do
|
129
|
+
class WithBothAndUnspecified < CouchRest::Model::Base
|
130
|
+
use_database TEST_SERVER.default_database
|
131
|
+
property :name, :accessible => true
|
132
|
+
property :admin, :default => false, :protected => true
|
133
|
+
property :phone, :default => 'unset phone number'
|
134
|
+
end
|
135
|
+
|
136
|
+
it { expect { WithBothAndUnspecified.new }.to_not raise_error }
|
137
|
+
|
138
|
+
it 'should assume that any unspecified property is protected by default' do
|
139
|
+
user = WithBothAndUnspecified.new(:name => 'will', :admin => true, :phone => '555-1234')
|
140
|
+
|
141
|
+
user.name.should == 'will'
|
142
|
+
user.admin.should == false
|
143
|
+
user.phone.should == 'unset phone number'
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "from database" do
|
149
|
+
class WithProtected < CouchRest::Model::Base
|
150
|
+
use_database TEST_SERVER.default_database
|
151
|
+
property :name
|
152
|
+
property :admin, :default => false, :protected => true
|
153
|
+
view_by :name
|
154
|
+
end
|
155
|
+
|
156
|
+
before(:each) do
|
157
|
+
@user = WithProtected.new
|
158
|
+
@user.name = "will"
|
159
|
+
@user.admin = true
|
160
|
+
@user.save!
|
161
|
+
end
|
162
|
+
|
163
|
+
def verify_attrs(user)
|
164
|
+
user.name.should == "will"
|
165
|
+
user.admin.should == true
|
166
|
+
end
|
167
|
+
|
168
|
+
it "Base#get should not strip protected attributes" do
|
169
|
+
reloaded = WithProtected.get( @user.id )
|
170
|
+
verify_attrs reloaded
|
171
|
+
end
|
172
|
+
|
173
|
+
it "Base#get! should not strip protected attributes" do
|
174
|
+
reloaded = WithProtected.get!( @user.id )
|
175
|
+
verify_attrs reloaded
|
176
|
+
end
|
177
|
+
|
178
|
+
it "Base#all should not strip protected attributes" do
|
179
|
+
# all creates a CollectionProxy
|
180
|
+
docs = WithProtected.all(:key => @user.id)
|
181
|
+
docs.length.should == 1
|
182
|
+
reloaded = docs.first
|
183
|
+
verify_attrs reloaded
|
184
|
+
end
|
185
|
+
|
186
|
+
it "views should not strip protected attributes" do
|
187
|
+
docs = WithProtected.by_name(:startkey => "will", :endkey => "will")
|
188
|
+
reloaded = docs.first
|
189
|
+
verify_attrs reloaded
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,481 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe CouchRest::Model::Property do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
reset_test_db!
|
8
|
+
@card = Card.new(:first_name => "matt")
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be accessible from the object" do
|
12
|
+
@card.properties.should be_an_instance_of(Array)
|
13
|
+
@card.properties.map{|p| p.name}.should include("first_name")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should list object properties with values" do
|
17
|
+
@card.properties_with_values.should be_an_instance_of(Hash)
|
18
|
+
@card.properties_with_values["first_name"].should == "matt"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should let you access a property value (getter)" do
|
22
|
+
@card.first_name.should == "matt"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should let you set a property value (setter)" do
|
26
|
+
@card.last_name = "Aimonetti"
|
27
|
+
@card.last_name.should == "Aimonetti"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not let you set a property value if it's read only" do
|
31
|
+
lambda{@card.read_only_value = "test"}.should raise_error
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should let you use an alias for an attribute" do
|
35
|
+
@card.last_name = "Aimonetti"
|
36
|
+
@card.family_name.should == "Aimonetti"
|
37
|
+
@card.family_name.should == @card.last_name
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should let you use an alias for a casted attribute" do
|
41
|
+
@card.cast_alias = Person.new(:name => ["Aimonetti"])
|
42
|
+
@card.cast_alias.name.should == ["Aimonetti"]
|
43
|
+
@card.calias.name.should == ["Aimonetti"]
|
44
|
+
card = Card.new(:first_name => "matt", :cast_alias => {:name => ["Aimonetti"]})
|
45
|
+
card.cast_alias.name.should == ["Aimonetti"]
|
46
|
+
card.calias.name.should == ["Aimonetti"]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should raise error if property name coincides with model type key" do
|
50
|
+
lambda { Cat.property(Cat.model_type_key) }.should raise_error(/already used/)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not raise error if property name coincides with model type key on non-model" do
|
54
|
+
lambda { Person.property(Article.model_type_key) }.should_not raise_error
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should be auto timestamped" do
|
58
|
+
@card.created_at.should be_nil
|
59
|
+
@card.updated_at.should be_nil
|
60
|
+
@card.save.should be_true
|
61
|
+
@card.created_at.should_not be_nil
|
62
|
+
@card.updated_at.should_not be_nil
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#as_couch_json" do
|
66
|
+
|
67
|
+
it "should provide a simple hash from model" do
|
68
|
+
@card.as_couch_json.class.should eql(Hash)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should remove properties from Hash if value is nil" do
|
72
|
+
@card.last_name = nil
|
73
|
+
@card.as_couch_json.keys.include?('last_name').should be_false
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#as_json" do
|
79
|
+
|
80
|
+
it "should provide a simple hash from model" do
|
81
|
+
@card.as_json.class.should eql(Hash)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should pass options to Active Support's as_json" do
|
85
|
+
@card.last_name = "Aimonetti"
|
86
|
+
@card.as_json(:only => 'last_name').should eql('last_name' => 'Aimonetti')
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#read_attribute' do
|
92
|
+
it "should let you use read_attribute method" do
|
93
|
+
@card.last_name = "Aimonetti"
|
94
|
+
@card.read_attribute(:last_name).should eql('Aimonetti')
|
95
|
+
@card.read_attribute('last_name').should eql('Aimonetti')
|
96
|
+
last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
|
97
|
+
@card.read_attribute(last_name_prop).should eql('Aimonetti')
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should raise an error if the property does not exist' do
|
101
|
+
expect { @card.read_attribute(:this_property_should_not_exist) }.to raise_error(ArgumentError)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '#write_attribute' do
|
106
|
+
it "should let you use write_attribute method" do
|
107
|
+
@card.write_attribute(:last_name, 'Aimonetti 1')
|
108
|
+
@card.last_name.should eql('Aimonetti 1')
|
109
|
+
@card.write_attribute('last_name', 'Aimonetti 2')
|
110
|
+
@card.last_name.should eql('Aimonetti 2')
|
111
|
+
last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
|
112
|
+
@card.write_attribute(last_name_prop, 'Aimonetti 3')
|
113
|
+
@card.last_name.should eql('Aimonetti 3')
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should raise an error if the property does not exist' do
|
117
|
+
expect { @card.write_attribute(:this_property_should_not_exist, 823) }.to raise_error(ArgumentError)
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
it "should let you use write_attribute on readonly properties" do
|
122
|
+
lambda {
|
123
|
+
@card.read_only_value = "foo"
|
124
|
+
}.should raise_error
|
125
|
+
@card.write_attribute(:read_only_value, "foo")
|
126
|
+
@card.read_only_value.should == 'foo'
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should cast via write_attribute" do
|
130
|
+
@card.write_attribute(:cast_alias, {:name => ["Sam", "Lown"]})
|
131
|
+
@card.cast_alias.class.should eql(Person)
|
132
|
+
@card.cast_alias.name.last.should eql("Lown")
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should not cast via write_attribute if property not casted" do
|
136
|
+
@card.write_attribute(:first_name, {:name => "Sam"})
|
137
|
+
@card.first_name.class.should eql(Hash)
|
138
|
+
@card.first_name[:name].should eql("Sam")
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "mass updating attributes without property" do
|
143
|
+
|
144
|
+
describe "when mass_assign_any_attribute false" do
|
145
|
+
|
146
|
+
it "should not allow them to be set" do
|
147
|
+
@card.attributes = {:test => 'fooobar'}
|
148
|
+
@card['test'].should be_nil
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "when mass_assign_any_attribute true" do
|
154
|
+
before(:each) do
|
155
|
+
# dup Card class so that no other tests are effected
|
156
|
+
card_class = Card.dup
|
157
|
+
card_class.class_eval do
|
158
|
+
mass_assign_any_attribute true
|
159
|
+
end
|
160
|
+
@card = card_class.new(:first_name => 'Sam')
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should allow them to be updated' do
|
164
|
+
@card.attributes = {:test => 'fooobar'}
|
165
|
+
@card['test'].should eql('fooobar')
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
describe "mass assignment protection" do
|
172
|
+
|
173
|
+
it "should not store protected attribute using mass assignment" do
|
174
|
+
cat_toy = CatToy.new(:name => "Zorro")
|
175
|
+
cat = Cat.create(:name => "Helena", :toys => [cat_toy], :favorite_toy => cat_toy, :number => 1)
|
176
|
+
cat.number.should be_nil
|
177
|
+
cat.number = 1
|
178
|
+
cat.save
|
179
|
+
cat.number.should == 1
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should not store protected attribute when 'declare accessible poperties, assume all the rest are protected'" do
|
183
|
+
user = User.create(:name => "Marcos Tapajós", :admin => true)
|
184
|
+
user.admin.should be_nil
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should not store protected attribute when 'declare protected properties, assume all the rest are accessible'" do
|
188
|
+
user = SpecialUser.create(:name => "Marcos Tapajós", :admin => true)
|
189
|
+
user.admin.should be_nil
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
describe "validation" do
|
195
|
+
before(:each) do
|
196
|
+
@invoice = Invoice.new(:client_name => "matt", :employee_name => "Chris", :location => "San Diego, CA")
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should be able to be validated" do
|
200
|
+
@card.valid?.should == true
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should let you validate the presence of an attribute" do
|
204
|
+
@card.first_name = nil
|
205
|
+
@card.should_not be_valid
|
206
|
+
@card.errors.should_not be_empty
|
207
|
+
@card.errors[:first_name].should == ["can't be blank"]
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should let you look up errors for a field by a string name" do
|
211
|
+
@card.first_name = nil
|
212
|
+
@card.should_not be_valid
|
213
|
+
@card.errors['first_name'].should == ["can't be blank"]
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should validate the presence of 2 attributes" do
|
217
|
+
@invoice.clear
|
218
|
+
@invoice.should_not be_valid
|
219
|
+
@invoice.errors.should_not be_empty
|
220
|
+
@invoice.errors[:client_name].should == ["can't be blank"]
|
221
|
+
@invoice.errors[:employee_name].should_not be_empty
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should let you set an error message" do
|
225
|
+
@invoice.location = nil
|
226
|
+
@invoice.valid?
|
227
|
+
@invoice.errors[:location].should == ["Hey stupid!, you forgot the location"]
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should validate before saving" do
|
231
|
+
@invoice.location = nil
|
232
|
+
@invoice.should_not be_valid
|
233
|
+
@invoice.save.should be_false
|
234
|
+
@invoice.should be_new
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
describe "properties of hash of casted models" do
|
241
|
+
it "should be able to assign a casted hash to a hash property" do
|
242
|
+
chain = KeyChain.new
|
243
|
+
keys = {"House" => "8==$", "Office" => "<>==U"}
|
244
|
+
chain.keys = keys
|
245
|
+
chain.keys = chain.keys
|
246
|
+
chain.keys.should == keys
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
describe "properties of array of casted models" do
|
251
|
+
|
252
|
+
before(:each) do
|
253
|
+
@course = Course.new :title => 'Test Course'
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should allow attribute to be set from an array of objects" do
|
257
|
+
@course.questions = [Question.new(:q => "works?"), Question.new(:q => "Meaning of Life?")]
|
258
|
+
@course.questions.length.should eql(2)
|
259
|
+
end
|
260
|
+
|
261
|
+
it "should allow attribute to be set from an array of hashes" do
|
262
|
+
@course.questions = [{:q => "works?"}, {:q => "Meaning of Life?"}]
|
263
|
+
@course.questions.length.should eql(2)
|
264
|
+
@course.questions.last.q.should eql("Meaning of Life?")
|
265
|
+
@course.questions.last.class.should eql(Question) # typecasting
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should allow attribute to be set from hash with ordered keys and objects" do
|
269
|
+
@course.questions = { '0' => Question.new(:q => "Test1"), '1' => Question.new(:q => 'Test2') }
|
270
|
+
@course.questions.length.should eql(2)
|
271
|
+
@course.questions.last.q.should eql('Test2')
|
272
|
+
@course.questions.last.class.should eql(Question)
|
273
|
+
end
|
274
|
+
|
275
|
+
it "should allow attribute to be set from hash with ordered keys and sub-hashes" do
|
276
|
+
@course.questions = { '10' => {:q => 'Test10'}, '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} }
|
277
|
+
@course.questions.length.should eql(3)
|
278
|
+
@course.questions.last.q.should eql('Test10')
|
279
|
+
@course.questions.last.class.should eql(Question)
|
280
|
+
end
|
281
|
+
|
282
|
+
it "should allow attribute to be set from hash with ordered keys and HashWithIndifferentAccess" do
|
283
|
+
# This is similar to what you'd find in an HTML POST parameters
|
284
|
+
hash = HashWithIndifferentAccess.new({ '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} })
|
285
|
+
@course.questions = hash
|
286
|
+
@course.questions.length.should eql(2)
|
287
|
+
@course.questions.last.q.should eql('Test2')
|
288
|
+
@course.questions.last.class.should eql(Question)
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
it "should raise an error if attempting to set single value for array type" do
|
293
|
+
lambda {
|
294
|
+
@course.questions = Question.new(:q => 'test1')
|
295
|
+
}.should raise_error(/Expecting an array/)
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
describe "a casted model retrieved from the database" do
|
302
|
+
before(:each) do
|
303
|
+
reset_test_db!
|
304
|
+
@cat = Cat.new(:name => 'Stimpy')
|
305
|
+
@cat.favorite_toy = CatToy.new(:name => 'Stinky')
|
306
|
+
@cat.toys << CatToy.new(:name => 'Feather')
|
307
|
+
@cat.toys << CatToy.new(:name => 'Mouse')
|
308
|
+
@cat.save
|
309
|
+
@cat = Cat.get(@cat.id)
|
310
|
+
end
|
311
|
+
|
312
|
+
describe "as a casted property" do
|
313
|
+
it "should already be casted_by its parent" do
|
314
|
+
@cat.favorite_toy.casted_by.should === @cat
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
describe "from a casted collection" do
|
319
|
+
it "should already be casted_by its parent" do
|
320
|
+
@cat.toys[0].casted_by.should === @cat
|
321
|
+
@cat.toys[1].casted_by.should === @cat
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
describe "nested models (not casted)" do
|
327
|
+
before(:each) do
|
328
|
+
reset_test_db!
|
329
|
+
@cat = ChildCat.new(:name => 'Stimpy')
|
330
|
+
@cat.mother = {:name => 'Stinky'}
|
331
|
+
@cat.siblings = [{:name => 'Feather'}, {:name => 'Felix'}]
|
332
|
+
@cat.save
|
333
|
+
@cat = ChildCat.get(@cat.id)
|
334
|
+
end
|
335
|
+
|
336
|
+
it "should correctly save single relation" do
|
337
|
+
@cat.mother.name.should eql('Stinky')
|
338
|
+
@cat.mother.casted_by.should eql(@cat)
|
339
|
+
end
|
340
|
+
|
341
|
+
it "should correctly save collection" do
|
342
|
+
@cat.siblings.first.name.should eql("Feather")
|
343
|
+
@cat.siblings.last.casted_by.should eql(@cat)
|
344
|
+
end
|
345
|
+
|
346
|
+
end
|
347
|
+
|
348
|
+
describe "Property Class" do
|
349
|
+
|
350
|
+
it "should provide name as string" do
|
351
|
+
property = CouchRest::Model::Property.new(:test, String)
|
352
|
+
property.name.should eql('test')
|
353
|
+
property.to_s.should eql('test')
|
354
|
+
end
|
355
|
+
|
356
|
+
it "should provide class from type" do
|
357
|
+
property = CouchRest::Model::Property.new(:test, String)
|
358
|
+
property.type_class.should eql(String)
|
359
|
+
end
|
360
|
+
|
361
|
+
it "should provide base class from type in array" do
|
362
|
+
property = CouchRest::Model::Property.new(:test, [String])
|
363
|
+
property.type_class.should eql(String)
|
364
|
+
end
|
365
|
+
|
366
|
+
it "should raise error if type as string requested" do
|
367
|
+
lambda {
|
368
|
+
property = CouchRest::Model::Property.new(:test, 'String')
|
369
|
+
}.should raise_error
|
370
|
+
end
|
371
|
+
|
372
|
+
it "should leave type nil and return class as nil also" do
|
373
|
+
property = CouchRest::Model::Property.new(:test, nil)
|
374
|
+
property.type.should be_nil
|
375
|
+
property.type_class.should be_nil
|
376
|
+
end
|
377
|
+
|
378
|
+
it "should convert empty type array to [Object]" do
|
379
|
+
property = CouchRest::Model::Property.new(:test, [])
|
380
|
+
property.type_class.should eql(Object)
|
381
|
+
end
|
382
|
+
|
383
|
+
it "should set init method option or leave as 'new'" do
|
384
|
+
# (bad example! Time already typecast)
|
385
|
+
property = CouchRest::Model::Property.new(:test, Time)
|
386
|
+
property.init_method.should eql('new')
|
387
|
+
property = CouchRest::Model::Property.new(:test, Time, :init_method => 'parse')
|
388
|
+
property.init_method.should eql('parse')
|
389
|
+
end
|
390
|
+
|
391
|
+
describe "#build" do
|
392
|
+
it "should allow instantiation of new object" do
|
393
|
+
property = CouchRest::Model::Property.new(:test, Date)
|
394
|
+
obj = property.build(2011, 05, 21)
|
395
|
+
obj.should eql(Date.new(2011, 05, 21))
|
396
|
+
end
|
397
|
+
it "should use init_method if provided" do
|
398
|
+
property = CouchRest::Model::Property.new(:test, Date, :init_method => 'parse')
|
399
|
+
obj = property.build("2011-05-21")
|
400
|
+
obj.should eql(Date.new(2011, 05, 21))
|
401
|
+
end
|
402
|
+
it "should use init_method Proc if provided" do
|
403
|
+
property = CouchRest::Model::Property.new(:test, Date, :init_method => Proc.new{|v| Date.parse(v)})
|
404
|
+
obj = property.build("2011-05-21")
|
405
|
+
obj.should eql(Date.new(2011, 05, 21))
|
406
|
+
end
|
407
|
+
it "should raise error if no class" do
|
408
|
+
property = CouchRest::Model::Property.new(:test)
|
409
|
+
lambda { property.build }.should raise_error(StandardError, /Cannot build/)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
## Property Casting method. More thoroughly tested in typecast_spec.
|
414
|
+
|
415
|
+
describe "casting" do
|
416
|
+
it "should cast a value" do
|
417
|
+
property = CouchRest::Model::Property.new(:test, Date)
|
418
|
+
parent = mock("FooObject")
|
419
|
+
property.cast(parent, "2010-06-16").should eql(Date.new(2010, 6, 16))
|
420
|
+
property.cast_value(parent, "2010-06-16").should eql(Date.new(2010, 6, 16))
|
421
|
+
end
|
422
|
+
|
423
|
+
it "should cast an array of values" do
|
424
|
+
property = CouchRest::Model::Property.new(:test, [Date])
|
425
|
+
parent = mock("FooObject")
|
426
|
+
property.cast(parent, ["2010-06-01", "2010-06-02"]).should eql([Date.new(2010, 6, 1), Date.new(2010, 6, 2)])
|
427
|
+
end
|
428
|
+
|
429
|
+
it "should set a CastedArray on array of Objects" do
|
430
|
+
property = CouchRest::Model::Property.new(:test, [Object])
|
431
|
+
parent = mock("FooObject")
|
432
|
+
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(CouchRest::Model::CastedArray)
|
433
|
+
end
|
434
|
+
|
435
|
+
it "should set a CastedArray on array of Strings" do
|
436
|
+
property = CouchRest::Model::Property.new(:test, [String])
|
437
|
+
parent = mock("FooObject")
|
438
|
+
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(CouchRest::Model::CastedArray)
|
439
|
+
end
|
440
|
+
|
441
|
+
it "should allow instantion of model via CastedArray#build" do
|
442
|
+
property = CouchRest::Model::Property.new(:dates, [Date])
|
443
|
+
parent = Article.new
|
444
|
+
ary = property.cast(parent, [])
|
445
|
+
obj = ary.build(2011, 05, 21)
|
446
|
+
ary.length.should eql(1)
|
447
|
+
ary.first.should eql(Date.new(2011, 05, 21))
|
448
|
+
obj = ary.build(2011, 05, 22)
|
449
|
+
ary.length.should eql(2)
|
450
|
+
ary.last.should eql(Date.new(2011, 05, 22))
|
451
|
+
end
|
452
|
+
|
453
|
+
it "should cast an object that provides an array" do
|
454
|
+
prop = Class.new do
|
455
|
+
attr_accessor :ary
|
456
|
+
def initialize(val); self.ary = val; end
|
457
|
+
def as_json; ary; end
|
458
|
+
end
|
459
|
+
property = CouchRest::Model::Property.new(:test, prop)
|
460
|
+
parent = mock("FooClass")
|
461
|
+
cast = property.cast(parent, [1, 2])
|
462
|
+
cast.ary.should eql([1, 2])
|
463
|
+
end
|
464
|
+
|
465
|
+
it "should set parent as casted_by object in CastedArray" do
|
466
|
+
property = CouchRest::Model::Property.new(:test, [Object])
|
467
|
+
parent = mock("FooObject")
|
468
|
+
property.cast(parent, ["2010-06-01", "2010-06-02"]).casted_by.should eql(parent)
|
469
|
+
end
|
470
|
+
|
471
|
+
it "should set casted_by on new value" do
|
472
|
+
property = CouchRest::Model::Property.new(:test, CatToy)
|
473
|
+
parent = mock("CatObject")
|
474
|
+
cast = property.cast(parent, {:name => 'catnip'})
|
475
|
+
cast.casted_by.should eql(parent)
|
476
|
+
end
|
477
|
+
|
478
|
+
end
|
479
|
+
|
480
|
+
end
|
481
|
+
|