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