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,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
|
+
|