couchrest_extended_document 1.0.0.beta5
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/LICENSE +176 -0
- data/README.md +68 -0
- data/Rakefile +68 -0
- data/THANKS.md +19 -0
- data/examples/model/example.rb +144 -0
- data/history.txt +159 -0
- data/lib/couchrest/casted_array.rb +25 -0
- data/lib/couchrest/casted_model.rb +55 -0
- data/lib/couchrest/extended_document.rb +323 -0
- data/lib/couchrest/mixins/attribute_protection.rb +74 -0
- data/lib/couchrest/mixins/callbacks.rb +532 -0
- data/lib/couchrest/mixins/class_proxy.rb +120 -0
- data/lib/couchrest/mixins/collection.rb +260 -0
- data/lib/couchrest/mixins/design_doc.rb +127 -0
- data/lib/couchrest/mixins/document_queries.rb +82 -0
- data/lib/couchrest/mixins/extended_attachments.rb +73 -0
- data/lib/couchrest/mixins/properties.rb +162 -0
- data/lib/couchrest/mixins/validation.rb +245 -0
- data/lib/couchrest/mixins/views.rb +148 -0
- data/lib/couchrest/mixins.rb +11 -0
- data/lib/couchrest/property.rb +50 -0
- data/lib/couchrest/support/couchrest.rb +19 -0
- data/lib/couchrest/support/rails.rb +42 -0
- data/lib/couchrest/typecast.rb +175 -0
- data/lib/couchrest/validation/auto_validate.rb +156 -0
- data/lib/couchrest/validation/contextual_validators.rb +78 -0
- data/lib/couchrest/validation/validation_errors.rb +125 -0
- data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
- data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
- data/lib/couchrest/validation/validators/format_validator.rb +122 -0
- data/lib/couchrest/validation/validators/formats/email.rb +66 -0
- data/lib/couchrest/validation/validators/formats/url.rb +43 -0
- data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
- data/lib/couchrest/validation/validators/length_validator.rb +139 -0
- data/lib/couchrest/validation/validators/method_validator.rb +89 -0
- data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
- data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
- data/lib/couchrest/validation.rb +245 -0
- data/lib/couchrest_extended_document.rb +21 -0
- data/spec/couchrest/attribute_protection_spec.rb +150 -0
- data/spec/couchrest/casted_extended_doc_spec.rb +79 -0
- data/spec/couchrest/casted_model_spec.rb +406 -0
- data/spec/couchrest/extended_doc_attachment_spec.rb +148 -0
- data/spec/couchrest/extended_doc_inherited_spec.rb +40 -0
- data/spec/couchrest/extended_doc_spec.rb +868 -0
- data/spec/couchrest/extended_doc_subclass_spec.rb +99 -0
- data/spec/couchrest/extended_doc_view_spec.rb +529 -0
- data/spec/couchrest/property_spec.rb +648 -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/more/article.rb +35 -0
- data/spec/fixtures/more/card.rb +22 -0
- data/spec/fixtures/more/cat.rb +22 -0
- data/spec/fixtures/more/course.rb +25 -0
- data/spec/fixtures/more/event.rb +8 -0
- data/spec/fixtures/more/invoice.rb +17 -0
- data/spec/fixtures/more/person.rb +9 -0
- data/spec/fixtures/more/question.rb +6 -0
- data/spec/fixtures/more/service.rb +12 -0
- data/spec/fixtures/more/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/spec.opts +5 -0
- data/spec/spec_helper.rb +49 -0
- data/utils/remap.rb +27 -0
- data/utils/subset.rb +30 -0
- metadata +200 -0
@@ -0,0 +1,406 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
4
|
+
require File.join(FIXTURE_PATH, 'more', 'person')
|
5
|
+
require File.join(FIXTURE_PATH, 'more', 'card')
|
6
|
+
require File.join(FIXTURE_PATH, 'more', 'cat')
|
7
|
+
require File.join(FIXTURE_PATH, 'more', 'question')
|
8
|
+
require File.join(FIXTURE_PATH, 'more', 'course')
|
9
|
+
|
10
|
+
|
11
|
+
class WithCastedModelMixin < Hash
|
12
|
+
include CouchRest::CastedModel
|
13
|
+
property :name
|
14
|
+
property :no_value
|
15
|
+
property :details, :type => 'Object', :default => {}
|
16
|
+
property :casted_attribute, :cast_as => 'WithCastedModelMixin'
|
17
|
+
end
|
18
|
+
|
19
|
+
class DummyModel < CouchRest::ExtendedDocument
|
20
|
+
use_database TEST_SERVER.default_database
|
21
|
+
raise "Default DB not set" if TEST_SERVER.default_database.nil?
|
22
|
+
property :casted_attribute, :cast_as => 'WithCastedModelMixin'
|
23
|
+
property :keywords, :cast_as => ["String"]
|
24
|
+
end
|
25
|
+
|
26
|
+
class CastedCallbackDoc < CouchRest::ExtendedDocument
|
27
|
+
use_database TEST_SERVER.default_database
|
28
|
+
raise "Default DB not set" if TEST_SERVER.default_database.nil?
|
29
|
+
property :callback_model, :cast_as => 'WithCastedCallBackModel'
|
30
|
+
end
|
31
|
+
class WithCastedCallBackModel < Hash
|
32
|
+
include CouchRest::CastedModel
|
33
|
+
include CouchRest::Validation
|
34
|
+
property :name
|
35
|
+
property :run_before_validate
|
36
|
+
property :run_after_validate
|
37
|
+
|
38
|
+
before_validate do |object|
|
39
|
+
object.run_before_validate = true
|
40
|
+
end
|
41
|
+
after_validate do |object|
|
42
|
+
object.run_after_validate = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe CouchRest::CastedModel do
|
47
|
+
|
48
|
+
describe "A non hash class including CastedModel" do
|
49
|
+
it "should fail raising and include error" do
|
50
|
+
lambda do
|
51
|
+
class NotAHashButWithCastedModelMixin
|
52
|
+
include CouchRest::CastedModel
|
53
|
+
property :name
|
54
|
+
end
|
55
|
+
|
56
|
+
end.should raise_error
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "isolated" do
|
61
|
+
before(:each) do
|
62
|
+
@obj = WithCastedModelMixin.new
|
63
|
+
end
|
64
|
+
it "should automatically include the property mixin and define getters and setters" do
|
65
|
+
@obj.name = 'Matt'
|
66
|
+
@obj.name.should == 'Matt'
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should allow override of default" do
|
70
|
+
@obj = WithCastedModelMixin.new(:name => 'Eric', :details => {'color' => 'orange'})
|
71
|
+
@obj.name.should == 'Eric'
|
72
|
+
@obj.details['color'].should == 'orange'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "casted as an attribute, but without a value" do
|
77
|
+
before(:each) do
|
78
|
+
@obj = DummyModel.new
|
79
|
+
@casted_obj = @obj.casted_attribute
|
80
|
+
end
|
81
|
+
it "should be nil" do
|
82
|
+
@casted_obj.should == nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "casted as attribute" do
|
87
|
+
before(:each) do
|
88
|
+
casted = {:name => 'not whatever'}
|
89
|
+
@obj = DummyModel.new(:casted_attribute => {:name => 'whatever', :casted_attribute => casted})
|
90
|
+
@casted_obj = @obj.casted_attribute
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should be available from its parent" do
|
94
|
+
@casted_obj.should be_an_instance_of(WithCastedModelMixin)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should have the getters defined" do
|
98
|
+
@casted_obj.name.should == 'whatever'
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should know who casted it" do
|
102
|
+
@casted_obj.casted_by.should == @obj
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should return nil for the 'no_value' attribute" do
|
106
|
+
@casted_obj.no_value.should be_nil
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should return nil for the unknown attribute" do
|
110
|
+
@casted_obj["unknown"].should be_nil
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should return {} for the hash attribute" do
|
114
|
+
@casted_obj.details.should == {}
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should cast its own attributes" do
|
118
|
+
@casted_obj.casted_attribute.should be_instance_of(WithCastedModelMixin)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "casted as an array of a different type" do
|
123
|
+
before(:each) do
|
124
|
+
@obj = DummyModel.new(:keywords => ['couch', 'sofa', 'relax', 'canapé'])
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should cast the array properly" do
|
128
|
+
@obj.keywords.should be_an_instance_of(Array)
|
129
|
+
@obj.keywords.first.should == 'couch'
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "update attributes without saving" do
|
134
|
+
before(:each) do
|
135
|
+
@question = Question.new(:q => "What is your quest?", :a => "To seek the Holy Grail")
|
136
|
+
end
|
137
|
+
it "should work for attribute= methods" do
|
138
|
+
@question.q.should == "What is your quest?"
|
139
|
+
@question['a'].should == "To seek the Holy Grail"
|
140
|
+
@question.update_attributes_without_saving(:q => "What is your favorite color?", 'a' => "Blue")
|
141
|
+
@question['q'].should == "What is your favorite color?"
|
142
|
+
@question.a.should == "Blue"
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should also work for attributes= alias" do
|
146
|
+
@question.respond_to?(:attributes=).should be_true
|
147
|
+
@question.attributes = {:q => "What is your favorite color?", 'a' => "Blue"}
|
148
|
+
@question['q'].should == "What is your favorite color?"
|
149
|
+
@question.a.should == "Blue"
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should flip out if an attribute= method is missing" do
|
153
|
+
lambda {
|
154
|
+
@q.update_attributes_without_saving('foo' => "something", :a => "No green")
|
155
|
+
}.should raise_error(NoMethodError)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should not change any attributes if there is an error" do
|
159
|
+
lambda {
|
160
|
+
@q.update_attributes_without_saving('foo' => "something", :a => "No green")
|
161
|
+
}.should raise_error(NoMethodError)
|
162
|
+
@question.q.should == "What is your quest?"
|
163
|
+
@question.a.should == "To seek the Holy Grail"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "saved document with casted models" do
|
168
|
+
before(:each) do
|
169
|
+
reset_test_db!
|
170
|
+
@obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
|
171
|
+
@obj.save.should be_true
|
172
|
+
@obj = DummyModel.get(@obj.id)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should be able to load with the casted models" do
|
176
|
+
casted_obj = @obj.casted_attribute
|
177
|
+
casted_obj.should_not be_nil
|
178
|
+
casted_obj.should be_an_instance_of(WithCastedModelMixin)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should have defined getters for the casted model" do
|
182
|
+
casted_obj = @obj.casted_attribute
|
183
|
+
casted_obj.name.should == "whatever"
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should have defined setters for the casted model" do
|
187
|
+
casted_obj = @obj.casted_attribute
|
188
|
+
casted_obj.name = "test"
|
189
|
+
casted_obj.name.should == "test"
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should retain an override of a casted model attribute's default" do
|
193
|
+
casted_obj = @obj.casted_attribute
|
194
|
+
casted_obj.details['color'] = 'orange'
|
195
|
+
@obj.save
|
196
|
+
casted_obj = DummyModel.get(@obj.id).casted_attribute
|
197
|
+
casted_obj.details['color'].should == 'orange'
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
describe "saving document with array of casted models and validation" do
|
203
|
+
before :each do
|
204
|
+
@cat = Cat.new :name => "felix"
|
205
|
+
@cat.save
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should save" do
|
209
|
+
toy = CatToy.new :name => "Mouse"
|
210
|
+
@cat.toys.push(toy)
|
211
|
+
@cat.save.should be_true
|
212
|
+
@cat = Cat.get @cat.id
|
213
|
+
@cat.toys.class.should == CouchRest::CastedArray
|
214
|
+
@cat.toys.first.class.should == CatToy
|
215
|
+
@cat.toys.first.should === toy
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should fail because name is not present" do
|
219
|
+
toy = CatToy.new
|
220
|
+
@cat.toys.push(toy)
|
221
|
+
@cat.should_not be_valid
|
222
|
+
@cat.save.should be_false
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should not fail if the casted model doesn't have validation" do
|
226
|
+
Cat.property :masters, :cast_as => ['Person'], :default => []
|
227
|
+
Cat.validates_presence_of :name
|
228
|
+
cat = Cat.new(:name => 'kitty')
|
229
|
+
cat.should be_valid
|
230
|
+
cat.masters.push Person.new
|
231
|
+
cat.should be_valid
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe "calling valid?" do
|
236
|
+
before :each do
|
237
|
+
@cat = Cat.new
|
238
|
+
@toy1 = CatToy.new
|
239
|
+
@toy2 = CatToy.new
|
240
|
+
@toy3 = CatToy.new
|
241
|
+
@cat.favorite_toy = @toy1
|
242
|
+
@cat.toys << @toy2
|
243
|
+
@cat.toys << @toy3
|
244
|
+
end
|
245
|
+
|
246
|
+
describe "on the top document" do
|
247
|
+
it "should put errors on all invalid casted models" do
|
248
|
+
@cat.should_not be_valid
|
249
|
+
@cat.errors.should_not be_empty
|
250
|
+
@toy1.errors.should_not be_empty
|
251
|
+
@toy2.errors.should_not be_empty
|
252
|
+
@toy3.errors.should_not be_empty
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should not put errors on valid casted models" do
|
256
|
+
@toy1.name = "Feather"
|
257
|
+
@toy2.name = "Twine"
|
258
|
+
@cat.should_not be_valid
|
259
|
+
@cat.errors.should_not be_empty
|
260
|
+
@toy1.errors.should be_empty
|
261
|
+
@toy2.errors.should be_empty
|
262
|
+
@toy3.errors.should_not be_empty
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe "on a casted model property" do
|
267
|
+
it "should only validate itself" do
|
268
|
+
@toy1.should_not be_valid
|
269
|
+
@toy1.errors.should_not be_empty
|
270
|
+
@cat.errors.should be_empty
|
271
|
+
@toy2.errors.should be_empty
|
272
|
+
@toy3.errors.should be_empty
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
describe "on a casted model inside a casted collection" do
|
277
|
+
it "should only validate itself" do
|
278
|
+
@toy2.should_not be_valid
|
279
|
+
@toy2.errors.should_not be_empty
|
280
|
+
@cat.errors.should be_empty
|
281
|
+
@toy1.errors.should be_empty
|
282
|
+
@toy3.errors.should be_empty
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
describe "calling new? on a casted model" do
|
288
|
+
before :each do
|
289
|
+
reset_test_db!
|
290
|
+
@cat = Cat.new(:name => 'Sockington')
|
291
|
+
@favorite_toy = CatToy.new(:name => 'Catnip Ball')
|
292
|
+
@cat.favorite_toy = @favorite_toy
|
293
|
+
@cat.toys << CatToy.new(:name => 'Fuzzy Stick')
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should be true on new" do
|
297
|
+
CatToy.new.should be_new
|
298
|
+
CatToy.new.new_record?.should be_true
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should be true after assignment" do
|
302
|
+
@cat.should be_new
|
303
|
+
@cat.favorite_toy.should be_new
|
304
|
+
@cat.toys.first.should be_new
|
305
|
+
end
|
306
|
+
|
307
|
+
it "should not be true after create or save" do
|
308
|
+
@cat.create
|
309
|
+
@cat.save
|
310
|
+
@cat.favorite_toy.should_not be_new
|
311
|
+
@cat.toys.first.should_not be_new
|
312
|
+
end
|
313
|
+
|
314
|
+
it "should not be true after get from the database" do
|
315
|
+
@cat.save
|
316
|
+
@cat = Cat.get(@cat.id)
|
317
|
+
@cat.favorite_toy.should_not be_new
|
318
|
+
@cat.toys.first.should_not be_new
|
319
|
+
end
|
320
|
+
|
321
|
+
it "should still be true after a failed create or save" do
|
322
|
+
@cat.name = nil
|
323
|
+
@cat.create.should be_false
|
324
|
+
@cat.save.should be_false
|
325
|
+
@cat.favorite_toy.should be_new
|
326
|
+
@cat.toys.first.should be_new
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
describe "calling base_doc from a nested casted model" do
|
331
|
+
before :each do
|
332
|
+
@course = Course.new(:title => 'Science 101')
|
333
|
+
@professor = Person.new(:name => 'Professor Plum')
|
334
|
+
@cat = Cat.new(:name => 'Scratchy')
|
335
|
+
@toy1 = CatToy.new
|
336
|
+
@toy2 = CatToy.new
|
337
|
+
@course.professor = @professor
|
338
|
+
@professor.pet = @cat
|
339
|
+
@cat.favorite_toy = @toy1
|
340
|
+
@cat.toys << @toy2
|
341
|
+
end
|
342
|
+
|
343
|
+
it "should reference the top document for" do
|
344
|
+
@course.base_doc.should === @course
|
345
|
+
@professor.casted_by.should === @course
|
346
|
+
@professor.base_doc.should === @course
|
347
|
+
@cat.base_doc.should === @course
|
348
|
+
@toy1.base_doc.should === @course
|
349
|
+
@toy2.base_doc.should === @course
|
350
|
+
end
|
351
|
+
|
352
|
+
it "should call setter on top document" do
|
353
|
+
@toy1.base_doc.should_not be_nil
|
354
|
+
@toy1.base_doc.title = 'Tom Foolery'
|
355
|
+
@course.title.should == 'Tom Foolery'
|
356
|
+
end
|
357
|
+
|
358
|
+
it "should return nil if not yet casted" do
|
359
|
+
person = Person.new
|
360
|
+
person.base_doc.should == nil
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
describe "calling base_doc.save from a nested casted model" do
|
365
|
+
before :each do
|
366
|
+
reset_test_db!
|
367
|
+
@cat = Cat.new(:name => 'Snowball')
|
368
|
+
@toy = CatToy.new
|
369
|
+
@cat.favorite_toy = @toy
|
370
|
+
end
|
371
|
+
|
372
|
+
it "should not save parent document when casted model is invalid" do
|
373
|
+
@toy.should_not be_valid
|
374
|
+
@toy.base_doc.save.should be_false
|
375
|
+
lambda{@toy.base_doc.save!}.should raise_error
|
376
|
+
end
|
377
|
+
|
378
|
+
it "should save parent document when nested casted model is valid" do
|
379
|
+
@toy.name = "Mr Squeaks"
|
380
|
+
@toy.should be_valid
|
381
|
+
@toy.base_doc.save.should be_true
|
382
|
+
lambda{@toy.base_doc.save!}.should_not raise_error
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
describe "callbacks" do
|
387
|
+
before(:each) do
|
388
|
+
@doc = CastedCallbackDoc.new
|
389
|
+
@model = WithCastedCallBackModel.new
|
390
|
+
@doc.callback_model = @model
|
391
|
+
end
|
392
|
+
|
393
|
+
describe "validate" do
|
394
|
+
it "should run before_validate before validating" do
|
395
|
+
@model.run_before_validate.should be_nil
|
396
|
+
@model.should be_valid
|
397
|
+
@model.run_before_validate.should be_true
|
398
|
+
end
|
399
|
+
it "should run after_validate after validating" do
|
400
|
+
@model.run_after_validate.should be_nil
|
401
|
+
@model.should be_valid
|
402
|
+
@model.run_after_validate.should be_true
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "ExtendedDocument attachments" do
|
4
|
+
|
5
|
+
describe "#has_attachment?" do
|
6
|
+
before(:each) do
|
7
|
+
reset_test_db!
|
8
|
+
@obj = Basic.new
|
9
|
+
@obj.save.should == true
|
10
|
+
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
11
|
+
@attachment_name = 'my_attachment'
|
12
|
+
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should return false if there is no attachment' do
|
16
|
+
@obj.has_attachment?('bogus').should be_false
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should return true if there is an attachment' do
|
20
|
+
@obj.has_attachment?(@attachment_name).should be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should return true if an object with an attachment is reloaded' do
|
24
|
+
@obj.save.should be_true
|
25
|
+
reloaded_obj = Basic.get(@obj.id)
|
26
|
+
reloaded_obj.has_attachment?(@attachment_name).should be_true
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should return false if an attachment has been removed' do
|
30
|
+
@obj.delete_attachment(@attachment_name)
|
31
|
+
@obj.has_attachment?(@attachment_name).should be_false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "creating an attachment" do
|
36
|
+
before(:each) do
|
37
|
+
@obj = Basic.new
|
38
|
+
@obj.save.should == true
|
39
|
+
@file_ext = File.open(FIXTURE_PATH + '/attachments/test.html')
|
40
|
+
@file_no_ext = File.open(FIXTURE_PATH + '/attachments/README')
|
41
|
+
@attachment_name = 'my_attachment'
|
42
|
+
@content_type = 'media/mp3'
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should create an attachment from file with an extension" do
|
46
|
+
@obj.create_attachment(:file => @file_ext, :name => @attachment_name)
|
47
|
+
@obj.save.should == true
|
48
|
+
reloaded_obj = Basic.get(@obj.id)
|
49
|
+
reloaded_obj['_attachments'][@attachment_name].should_not be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should create an attachment from file without an extension" do
|
53
|
+
@obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
|
54
|
+
@obj.save.should == true
|
55
|
+
reloaded_obj = Basic.get(@obj.id)
|
56
|
+
reloaded_obj['_attachments'][@attachment_name].should_not be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should raise ArgumentError if :file is missing' do
|
60
|
+
lambda{ @obj.create_attachment(:name => @attachment_name) }.should raise_error
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should raise ArgumentError if :name is missing' do
|
64
|
+
lambda{ @obj.create_attachment(:file => @file_ext) }.should raise_error
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should set the content-type if passed' do
|
68
|
+
@obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
|
69
|
+
@obj['_attachments'][@attachment_name]['content_type'].should == @content_type
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should detect the content-type automatically" do
|
73
|
+
@obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
|
74
|
+
@obj['_attachments']['couchdb.png']['content_type'].should == "image/png"
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should use name to detect the content-type automatically if no file" do
|
78
|
+
file = File.open(FIXTURE_PATH + '/attachments/couchdb.png')
|
79
|
+
file.stub!(:path).and_return("badfilname")
|
80
|
+
@obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
|
81
|
+
@obj['_attachments']['couchdb.png']['content_type'].should == "image/png"
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'reading, updating, and deleting an attachment' do
|
87
|
+
before(:each) do
|
88
|
+
@obj = Basic.new
|
89
|
+
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
90
|
+
@attachment_name = 'my_attachment'
|
91
|
+
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
92
|
+
@obj.save.should == true
|
93
|
+
@file.rewind
|
94
|
+
@content_type = 'media/mp3'
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should read an attachment that exists' do
|
98
|
+
@obj.read_attachment(@attachment_name).should == @file.read
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should update an attachment that exists' do
|
102
|
+
file = File.open(FIXTURE_PATH + '/attachments/README')
|
103
|
+
@file.should_not == file
|
104
|
+
@obj.update_attachment(:file => file, :name => @attachment_name)
|
105
|
+
@obj.save
|
106
|
+
reloaded_obj = Basic.get(@obj.id)
|
107
|
+
file.rewind
|
108
|
+
reloaded_obj.read_attachment(@attachment_name).should_not == @file.read
|
109
|
+
reloaded_obj.read_attachment(@attachment_name).should == file.read
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should set the content-type if passed' do
|
113
|
+
file = File.open(FIXTURE_PATH + '/attachments/README')
|
114
|
+
@file.should_not == file
|
115
|
+
@obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
|
116
|
+
@obj['_attachments'][@attachment_name]['content_type'].should == @content_type
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should delete an attachment that exists' do
|
120
|
+
@obj.delete_attachment(@attachment_name)
|
121
|
+
@obj.save
|
122
|
+
lambda{Basic.get(@obj.id).read_attachment(@attachment_name)}.should raise_error
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "#attachment_url" do
|
127
|
+
before(:each) do
|
128
|
+
@obj = Basic.new
|
129
|
+
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
130
|
+
@attachment_name = 'my_attachment'
|
131
|
+
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
132
|
+
@obj.save.should == true
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should return nil if attachment does not exist' do
|
136
|
+
@obj.attachment_url('bogus').should be_nil
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should return the attachment URL as specified by CouchDB HttpDocumentApi' do
|
140
|
+
@obj.attachment_url(@attachment_name).should == "#{Basic.database}/#{@obj.id}/#{@attachment_name}"
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should return the attachment URI' do
|
144
|
+
@obj.attachment_uri(@attachment_name).should == "#{Basic.database.uri}/#{@obj.id}/#{@attachment_name}"
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems' unless ENV['SKIP_RUBYGEMS']
|
5
|
+
require 'active_support/json'
|
6
|
+
ActiveSupport::JSON.backend = :JSONGem
|
7
|
+
|
8
|
+
class PlainParent
|
9
|
+
class_inheritable_accessor :foo
|
10
|
+
self.foo = :bar
|
11
|
+
end
|
12
|
+
|
13
|
+
class PlainChild < PlainParent
|
14
|
+
end
|
15
|
+
|
16
|
+
class ExtendedParent < CouchRest::ExtendedDocument
|
17
|
+
class_inheritable_accessor :foo
|
18
|
+
self.foo = :bar
|
19
|
+
end
|
20
|
+
|
21
|
+
class ExtendedChild < ExtendedParent
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "Using chained inheritance without CouchRest::ExtendedDocument" do
|
25
|
+
it "should preserve inheritable attributes" do
|
26
|
+
PlainParent.foo.should == :bar
|
27
|
+
PlainChild.foo.should == :bar
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "Using chained inheritance with CouchRest::ExtendedDocument" do
|
32
|
+
it "should preserve inheritable attributes" do
|
33
|
+
ExtendedParent.foo.should == :bar
|
34
|
+
ExtendedChild.foo.should == :bar
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
rescue LoadError
|
39
|
+
puts "This spec requires 'active_support/json' to be loaded"
|
40
|
+
end
|