openlogic-couchrest_model 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +4 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +176 -0
  5. data/README.md +137 -0
  6. data/Rakefile +38 -0
  7. data/THANKS.md +21 -0
  8. data/VERSION +1 -0
  9. data/benchmarks/dirty.rb +118 -0
  10. data/couchrest_model.gemspec +36 -0
  11. data/history.md +309 -0
  12. data/init.rb +1 -0
  13. data/lib/couchrest/model.rb +10 -0
  14. data/lib/couchrest/model/associations.rb +231 -0
  15. data/lib/couchrest/model/base.rb +129 -0
  16. data/lib/couchrest/model/callbacks.rb +28 -0
  17. data/lib/couchrest/model/casted_array.rb +83 -0
  18. data/lib/couchrest/model/casted_by.rb +33 -0
  19. data/lib/couchrest/model/casted_hash.rb +84 -0
  20. data/lib/couchrest/model/class_proxy.rb +135 -0
  21. data/lib/couchrest/model/collection.rb +273 -0
  22. data/lib/couchrest/model/configuration.rb +67 -0
  23. data/lib/couchrest/model/connection.rb +70 -0
  24. data/lib/couchrest/model/core_extensions/hash.rb +9 -0
  25. data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
  26. data/lib/couchrest/model/design_doc.rb +128 -0
  27. data/lib/couchrest/model/designs.rb +91 -0
  28. data/lib/couchrest/model/designs/view.rb +513 -0
  29. data/lib/couchrest/model/dirty.rb +39 -0
  30. data/lib/couchrest/model/document_queries.rb +99 -0
  31. data/lib/couchrest/model/embeddable.rb +78 -0
  32. data/lib/couchrest/model/errors.rb +25 -0
  33. data/lib/couchrest/model/extended_attachments.rb +83 -0
  34. data/lib/couchrest/model/persistence.rb +178 -0
  35. data/lib/couchrest/model/properties.rb +228 -0
  36. data/lib/couchrest/model/property.rb +114 -0
  37. data/lib/couchrest/model/property_protection.rb +71 -0
  38. data/lib/couchrest/model/proxyable.rb +183 -0
  39. data/lib/couchrest/model/support/couchrest_database.rb +13 -0
  40. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  41. data/lib/couchrest/model/typecast.rb +154 -0
  42. data/lib/couchrest/model/validations.rb +80 -0
  43. data/lib/couchrest/model/validations/casted_model.rb +16 -0
  44. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  45. data/lib/couchrest/model/validations/uniqueness.rb +69 -0
  46. data/lib/couchrest/model/views.rb +151 -0
  47. data/lib/couchrest/railtie.rb +24 -0
  48. data/lib/couchrest_model.rb +66 -0
  49. data/lib/rails/generators/couchrest_model.rb +16 -0
  50. data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
  51. data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
  52. data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
  53. data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
  54. data/spec/.gitignore +1 -0
  55. data/spec/fixtures/attachments/README +3 -0
  56. data/spec/fixtures/attachments/couchdb.png +0 -0
  57. data/spec/fixtures/attachments/test.html +11 -0
  58. data/spec/fixtures/config/couchdb.yml +10 -0
  59. data/spec/fixtures/models/article.rb +36 -0
  60. data/spec/fixtures/models/base.rb +164 -0
  61. data/spec/fixtures/models/card.rb +19 -0
  62. data/spec/fixtures/models/cat.rb +23 -0
  63. data/spec/fixtures/models/client.rb +6 -0
  64. data/spec/fixtures/models/course.rb +27 -0
  65. data/spec/fixtures/models/event.rb +8 -0
  66. data/spec/fixtures/models/invoice.rb +14 -0
  67. data/spec/fixtures/models/key_chain.rb +5 -0
  68. data/spec/fixtures/models/membership.rb +4 -0
  69. data/spec/fixtures/models/person.rb +11 -0
  70. data/spec/fixtures/models/project.rb +6 -0
  71. data/spec/fixtures/models/question.rb +7 -0
  72. data/spec/fixtures/models/sale_entry.rb +9 -0
  73. data/spec/fixtures/models/sale_invoice.rb +14 -0
  74. data/spec/fixtures/models/service.rb +10 -0
  75. data/spec/fixtures/models/user.rb +22 -0
  76. data/spec/fixtures/views/lib.js +3 -0
  77. data/spec/fixtures/views/test_view/lib.js +3 -0
  78. data/spec/fixtures/views/test_view/only-map.js +4 -0
  79. data/spec/fixtures/views/test_view/test-map.js +3 -0
  80. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  81. data/spec/functional/validations_spec.rb +8 -0
  82. data/spec/spec_helper.rb +60 -0
  83. data/spec/unit/active_model_lint_spec.rb +30 -0
  84. data/spec/unit/assocations_spec.rb +242 -0
  85. data/spec/unit/attachment_spec.rb +176 -0
  86. data/spec/unit/base_spec.rb +537 -0
  87. data/spec/unit/casted_spec.rb +72 -0
  88. data/spec/unit/class_proxy_spec.rb +167 -0
  89. data/spec/unit/collection_spec.rb +86 -0
  90. data/spec/unit/configuration_spec.rb +77 -0
  91. data/spec/unit/connection_spec.rb +148 -0
  92. data/spec/unit/core_extensions/time_parsing.rb +77 -0
  93. data/spec/unit/design_doc_spec.rb +241 -0
  94. data/spec/unit/designs/view_spec.rb +831 -0
  95. data/spec/unit/designs_spec.rb +134 -0
  96. data/spec/unit/dirty_spec.rb +436 -0
  97. data/spec/unit/embeddable_spec.rb +498 -0
  98. data/spec/unit/inherited_spec.rb +33 -0
  99. data/spec/unit/persistence_spec.rb +481 -0
  100. data/spec/unit/property_protection_spec.rb +192 -0
  101. data/spec/unit/property_spec.rb +481 -0
  102. data/spec/unit/proxyable_spec.rb +376 -0
  103. data/spec/unit/subclass_spec.rb +85 -0
  104. data/spec/unit/typecast_spec.rb +521 -0
  105. data/spec/unit/validations_spec.rb +140 -0
  106. data/spec/unit/view_spec.rb +367 -0
  107. 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
+