davber_couchrest_extended_document 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 (71) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +250 -0
  3. data/Rakefile +69 -0
  4. data/THANKS.md +22 -0
  5. data/examples/model/example.rb +144 -0
  6. data/history.txt +165 -0
  7. data/lib/couchrest/casted_array.rb +39 -0
  8. data/lib/couchrest/casted_model.rb +53 -0
  9. data/lib/couchrest/extended_document.rb +262 -0
  10. data/lib/couchrest/mixins.rb +12 -0
  11. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  12. data/lib/couchrest/mixins/attributes.rb +75 -0
  13. data/lib/couchrest/mixins/callbacks.rb +534 -0
  14. data/lib/couchrest/mixins/class_proxy.rb +120 -0
  15. data/lib/couchrest/mixins/collection.rb +260 -0
  16. data/lib/couchrest/mixins/design_doc.rb +159 -0
  17. data/lib/couchrest/mixins/document_queries.rb +82 -0
  18. data/lib/couchrest/mixins/extended_attachments.rb +73 -0
  19. data/lib/couchrest/mixins/properties.rb +130 -0
  20. data/lib/couchrest/mixins/typecast.rb +174 -0
  21. data/lib/couchrest/mixins/views.rb +148 -0
  22. data/lib/couchrest/property.rb +96 -0
  23. data/lib/couchrest/support/couchrest.rb +19 -0
  24. data/lib/couchrest/support/rails.rb +42 -0
  25. data/lib/couchrest/validation.rb +246 -0
  26. data/lib/couchrest/validation/auto_validate.rb +156 -0
  27. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  28. data/lib/couchrest/validation/validation_errors.rb +125 -0
  29. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  30. data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
  31. data/lib/couchrest/validation/validators/format_validator.rb +122 -0
  32. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  33. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  34. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  35. data/lib/couchrest/validation/validators/length_validator.rb +139 -0
  36. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  37. data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
  38. data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
  39. data/lib/couchrest_extended_document.rb +23 -0
  40. data/spec/couchrest/attribute_protection_spec.rb +150 -0
  41. data/spec/couchrest/casted_extended_doc_spec.rb +79 -0
  42. data/spec/couchrest/casted_model_spec.rb +424 -0
  43. data/spec/couchrest/extended_doc_attachment_spec.rb +148 -0
  44. data/spec/couchrest/extended_doc_inherited_spec.rb +40 -0
  45. data/spec/couchrest/extended_doc_spec.rb +869 -0
  46. data/spec/couchrest/extended_doc_subclass_spec.rb +101 -0
  47. data/spec/couchrest/extended_doc_view_spec.rb +529 -0
  48. data/spec/couchrest/property_spec.rb +790 -0
  49. data/spec/fixtures/attachments/README +3 -0
  50. data/spec/fixtures/attachments/couchdb.png +0 -0
  51. data/spec/fixtures/attachments/test.html +11 -0
  52. data/spec/fixtures/more/article.rb +35 -0
  53. data/spec/fixtures/more/card.rb +22 -0
  54. data/spec/fixtures/more/cat.rb +22 -0
  55. data/spec/fixtures/more/course.rb +25 -0
  56. data/spec/fixtures/more/event.rb +8 -0
  57. data/spec/fixtures/more/invoice.rb +17 -0
  58. data/spec/fixtures/more/person.rb +9 -0
  59. data/spec/fixtures/more/question.rb +7 -0
  60. data/spec/fixtures/more/service.rb +12 -0
  61. data/spec/fixtures/more/user.rb +22 -0
  62. data/spec/fixtures/views/lib.js +3 -0
  63. data/spec/fixtures/views/test_view/lib.js +3 -0
  64. data/spec/fixtures/views/test_view/only-map.js +4 -0
  65. data/spec/fixtures/views/test_view/test-map.js +3 -0
  66. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  67. data/spec/spec.opts +5 -0
  68. data/spec/spec_helper.rb +49 -0
  69. data/utils/remap.rb +27 -0
  70. data/utils/subset.rb +30 -0
  71. metadata +225 -0
@@ -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
@@ -0,0 +1,869 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path("../../spec_helper", __FILE__)
4
+ require File.join(FIXTURE_PATH, 'more', 'article')
5
+ require File.join(FIXTURE_PATH, 'more', 'course')
6
+ require File.join(FIXTURE_PATH, 'more', 'card')
7
+ require File.join(FIXTURE_PATH, 'more', 'cat')
8
+
9
+ describe "ExtendedDocument" do
10
+
11
+ class WithDefaultValues < CouchRest::ExtendedDocument
12
+ use_database TEST_SERVER.default_database
13
+ property :preset, :type => 'Object', :default => {:right => 10, :top_align => false}
14
+ property :set_by_proc, :default => Proc.new{Time.now}, :cast_as => 'Time'
15
+ property :tags, :type => ['String'], :default => []
16
+ property :read_only_with_default, :default => 'generic', :read_only => true
17
+ property :default_false, :type => 'Boolean', :default => false
18
+ property :name
19
+ timestamps!
20
+ end
21
+
22
+ class WithSimplePropertyType < CouchRest::ExtendedDocument
23
+ use_database TEST_SERVER.default_database
24
+ property :name, String
25
+ property :preset, String, :default => 'none'
26
+ property :tags, [String]
27
+ timestamps!
28
+ end
29
+
30
+ class WithCallBacks < CouchRest::ExtendedDocument
31
+ include ::CouchRest::Validation
32
+ use_database TEST_SERVER.default_database
33
+ property :name
34
+ property :run_before_validate
35
+ property :run_after_validate
36
+ property :run_before_save
37
+ property :run_after_save
38
+ property :run_before_create
39
+ property :run_after_create
40
+ property :run_before_update
41
+ property :run_after_update
42
+
43
+ before_validate do |object|
44
+ object.run_before_validate = true
45
+ end
46
+ after_validate do |object|
47
+ object.run_after_validate = true
48
+ end
49
+ before_save do |object|
50
+ object.run_before_save = true
51
+ end
52
+ after_save do |object|
53
+ object.run_after_save = true
54
+ end
55
+ before_create do |object|
56
+ object.run_before_create = true
57
+ end
58
+ after_create do |object|
59
+ object.run_after_create = true
60
+ end
61
+ before_update do |object|
62
+ object.run_before_update = true
63
+ end
64
+ after_update do |object|
65
+ object.run_after_update = true
66
+ end
67
+
68
+ property :run_one
69
+ property :run_two
70
+ property :run_three
71
+
72
+ before_save :run_one_method, :run_two_method do |object|
73
+ object.run_three = true
74
+ end
75
+ def run_one_method
76
+ self.run_one = true
77
+ end
78
+ def run_two_method
79
+ self.run_two = true
80
+ end
81
+
82
+ attr_accessor :run_it
83
+ property :conditional_one
84
+ property :conditional_two
85
+
86
+ before_save :conditional_one_method, :conditional_two_method, :if => proc { self.run_it }
87
+ def conditional_one_method
88
+ self.conditional_one = true
89
+ end
90
+ def conditional_two_method
91
+ self.conditional_two = true
92
+ end
93
+ end
94
+
95
+ class WithTemplateAndUniqueID < CouchRest::ExtendedDocument
96
+ use_database TEST_SERVER.default_database
97
+ unique_id do |model|
98
+ model['important-field']
99
+ end
100
+ property :preset, :default => 'value'
101
+ property :has_no_default
102
+ end
103
+
104
+ class WithGetterAndSetterMethods < CouchRest::ExtendedDocument
105
+ use_database TEST_SERVER.default_database
106
+
107
+ property :other_arg
108
+ def arg
109
+ other_arg
110
+ end
111
+
112
+ def arg=(value)
113
+ self.other_arg = "foo-#{value}"
114
+ end
115
+ end
116
+
117
+ class WithAfterInitializeMethod < CouchRest::ExtendedDocument
118
+ use_database TEST_SERVER.default_database
119
+
120
+ property :some_value
121
+
122
+ def after_initialize
123
+ self.some_value ||= "value"
124
+ end
125
+
126
+ end
127
+
128
+
129
+ before(:each) do
130
+ @obj = WithDefaultValues.new
131
+ end
132
+
133
+ describe "instance database connection" do
134
+ it "should use the default database" do
135
+ @obj.database.name.should == 'couchrest-test'
136
+ end
137
+
138
+ it "should override the default db" do
139
+ @obj.database = TEST_SERVER.database!('couchrest-extendedmodel-test')
140
+ @obj.database.name.should == 'couchrest-extendedmodel-test'
141
+ @obj.database.delete!
142
+ end
143
+ end
144
+
145
+ describe "a new model" do
146
+ it "should be a new document" do
147
+ @obj = Basic.new
148
+ @obj.rev.should be_nil
149
+ @obj.should be_new
150
+ @obj.should be_new_document
151
+ @obj.should be_new_record
152
+ end
153
+
154
+ it "should not failed on a nil value in argument" do
155
+ @obj = Basic.new(nil)
156
+ @obj.should == { CouchRest.type_field => 'Basic' }
157
+ end
158
+ end
159
+
160
+ describe "creating a new document" do
161
+ it "should instantialize and save a document" do
162
+ article = Article.create(:title => 'my test')
163
+ article.title.should == 'my test'
164
+ article.should_not be_new
165
+ end
166
+
167
+ it "should trigger the create callbacks" do
168
+ doc = WithCallBacks.create(:name => 'my other test')
169
+ doc.run_before_create.should be_true
170
+ doc.run_after_create.should be_true
171
+ doc.run_before_save.should be_true
172
+ doc.run_after_save.should be_true
173
+ end
174
+ end
175
+
176
+ describe "creating a new document from database" do
177
+
178
+ it "should instantialize" do
179
+ doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, CouchRest.type_field => 'Article', 'name' => 'my test'})
180
+ doc.class.should eql(Article)
181
+ end
182
+
183
+ it "should instantialize of same class if no type field included from DB" do
184
+ doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, 'name' => 'my test'})
185
+ doc.class.should eql(Article)
186
+ end
187
+
188
+ it "should instantialize document of different type" do
189
+ doc = Article.create_from_database({'_id' => 'testitem2', '_rev' => 123, CouchRest.type_field => 'WithCallBacks', 'name' => 'my test'})
190
+ doc.class.should eql(WithCallBacks)
191
+ end
192
+
193
+ end
194
+
195
+ describe "update attributes without saving" do
196
+ before(:each) do
197
+ a = Article.get "big-bad-danger" rescue nil
198
+ a.destroy if a
199
+ @art = Article.new(:title => "big bad danger")
200
+ @art.save
201
+ end
202
+ it "should work for attribute= methods" do
203
+ @art['title'].should == "big bad danger"
204
+ @art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
205
+ @art['title'].should == "super danger"
206
+ end
207
+ it "should silently ignore _id" do
208
+ @art.update_attributes_without_saving('_id' => 'foobar')
209
+ @art['_id'].should_not == 'foobar'
210
+ end
211
+ it "should silently ignore _rev" do
212
+ @art.update_attributes_without_saving('_rev' => 'foobar')
213
+ @art['_rev'].should_not == 'foobar'
214
+ end
215
+ it "should silently ignore created_at" do
216
+ @art.update_attributes_without_saving('created_at' => 'foobar')
217
+ @art['created_at'].should_not == 'foobar'
218
+ end
219
+ it "should silently ignore updated_at" do
220
+ @art.update_attributes_without_saving('updated_at' => 'foobar')
221
+ @art['updated_at'].should_not == 'foobar'
222
+ end
223
+ it "should also work using attributes= alias" do
224
+ @art.respond_to?(:attributes=).should be_true
225
+ @art.attributes = {'date' => Time.now, :title => "something else"}
226
+ @art['title'].should == "something else"
227
+ end
228
+
229
+ it "should not flip out if an attribute= method is missing and ignore it" do
230
+ lambda {
231
+ @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
232
+ }.should_not raise_error
233
+ @art.slug.should == "big-bad-danger"
234
+ end
235
+
236
+ #it "should not change other attributes if there is an error" do
237
+ # lambda {
238
+ # @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
239
+ # }.should raise_error
240
+ # @art['title'].should == "big bad danger"
241
+ #end
242
+ end
243
+
244
+ describe "update attributes" do
245
+ before(:each) do
246
+ a = Article.get "big-bad-danger" rescue nil
247
+ a.destroy if a
248
+ @art = Article.new(:title => "big bad danger")
249
+ @art.save
250
+ end
251
+ it "should save" do
252
+ @art['title'].should == "big bad danger"
253
+ @art.update_attributes('date' => Time.now, :title => "super danger")
254
+ loaded = Article.get(@art.id)
255
+ loaded['title'].should == "super danger"
256
+ end
257
+ end
258
+
259
+ describe "with default" do
260
+ it "should have the default value set at initalization" do
261
+ @obj.preset.should == {:right => 10, :top_align => false}
262
+ end
263
+
264
+ it "should have the default false value explicitly assigned" do
265
+ @obj.default_false.should == false
266
+ end
267
+
268
+ it "should automatically call a proc default at initialization" do
269
+ @obj.set_by_proc.should be_an_instance_of(Time)
270
+ @obj.set_by_proc.should == @obj.set_by_proc
271
+ @obj.set_by_proc.should < Time.now
272
+ end
273
+
274
+ it "should let you overwrite the default values" do
275
+ obj = WithDefaultValues.new(:preset => 'test')
276
+ obj.preset = 'test'
277
+ end
278
+
279
+ it "should work with a default empty array" do
280
+ obj = WithDefaultValues.new(:tags => ['spec'])
281
+ obj.tags.should == ['spec']
282
+ end
283
+
284
+ it "should set default value of read-only property" do
285
+ obj = WithDefaultValues.new
286
+ obj.read_only_with_default.should == 'generic'
287
+ end
288
+ end
289
+
290
+ describe "simplified way of setting property types" do
291
+ it "should set defaults" do
292
+ obj = WithSimplePropertyType.new
293
+ obj.preset.should eql('none')
294
+ end
295
+
296
+ it "should handle arrays" do
297
+ obj = WithSimplePropertyType.new(:tags => ['spec'])
298
+ obj.tags.should == ['spec']
299
+ end
300
+ end
301
+
302
+ describe "a doc with template values (CR::Model spec)" do
303
+ before(:all) do
304
+ WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
305
+ WithTemplateAndUniqueID.database.bulk_delete
306
+ @tmpl = WithTemplateAndUniqueID.new
307
+ @tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1')
308
+ end
309
+ it "should have fields set when new" do
310
+ @tmpl.preset.should == 'value'
311
+ end
312
+ it "shouldn't override explicitly set values" do
313
+ @tmpl2.preset.should == 'not_value'
314
+ end
315
+ it "shouldn't override existing documents" do
316
+ @tmpl2.save
317
+ tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
318
+ @tmpl2.preset.should == 'not_value'
319
+ tmpl2_reloaded.preset.should == 'not_value'
320
+ end
321
+ end
322
+
323
+ describe "getting a model" do
324
+ before(:all) do
325
+ @art = Article.new(:title => 'All About Getting')
326
+ @art.save
327
+ end
328
+ it "should load and instantiate it" do
329
+ foundart = Article.get @art.id
330
+ foundart.title.should == "All About Getting"
331
+ end
332
+ it "should load and instantiate with find" do
333
+ foundart = Article.find @art.id
334
+ foundart.title.should == "All About Getting"
335
+ end
336
+ it "should return nil if `get` is used and the document doesn't exist" do
337
+ foundart = Article.get 'matt aimonetti'
338
+ foundart.should be_nil
339
+ end
340
+ it "should raise an error if `get!` is used and the document doesn't exist" do
341
+ lambda{foundart = Article.get!('matt aimonetti')}.should raise_error
342
+ end
343
+ it "should raise an error if `find!` is used and the document doesn't exist" do
344
+ lambda{foundart = Article.find!('matt aimonetti')}.should raise_error
345
+ end
346
+ end
347
+
348
+ describe "getting a model with a subobjects array" do
349
+ before(:all) do
350
+ course_doc = {
351
+ "title" => "Metaphysics 200",
352
+ "questions" => [
353
+ {
354
+ "q" => "Carve the ___ of reality at the ___.",
355
+ "a" => ["beast","joints"]
356
+ },{
357
+ "q" => "Who layed the smack down on Leibniz's Law?",
358
+ "a" => "Willard Van Orman Quine"
359
+ }
360
+ ]
361
+ }
362
+ r = Course.database.save_doc course_doc
363
+ @course = Course.get r['id']
364
+ end
365
+ it "should load the course" do
366
+ @course.title.should == "Metaphysics 200"
367
+ end
368
+ it "should instantiate them as such" do
369
+ @course["questions"][0].a[0].should == "beast"
370
+ end
371
+ end
372
+
373
+ describe "finding all instances of a model" do
374
+ before(:all) do
375
+ WithTemplateAndUniqueID.req_design_doc_refresh
376
+ WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
377
+ WithTemplateAndUniqueID.database.bulk_delete
378
+ WithTemplateAndUniqueID.new('important-field' => '1').save
379
+ WithTemplateAndUniqueID.new('important-field' => '2').save
380
+ WithTemplateAndUniqueID.new('important-field' => '3').save
381
+ WithTemplateAndUniqueID.new('important-field' => '4').save
382
+ end
383
+ it "should make the design doc" do
384
+ WithTemplateAndUniqueID.all
385
+ d = WithTemplateAndUniqueID.design_doc
386
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
387
+ end
388
+ it "should find all" do
389
+ rs = WithTemplateAndUniqueID.all
390
+ rs.length.should == 4
391
+ end
392
+ end
393
+
394
+ describe "counting all instances of a model" do
395
+ before(:each) do
396
+ @db = reset_test_db!
397
+ WithTemplateAndUniqueID.req_design_doc_refresh
398
+ end
399
+
400
+ it ".count should return 0 if there are no docuemtns" do
401
+ WithTemplateAndUniqueID.count.should == 0
402
+ end
403
+
404
+ it ".count should return the number of documents" do
405
+ WithTemplateAndUniqueID.new('important-field' => '1').save
406
+ WithTemplateAndUniqueID.new('important-field' => '2').save
407
+ WithTemplateAndUniqueID.new('important-field' => '3').save
408
+
409
+ WithTemplateAndUniqueID.count.should == 3
410
+ end
411
+ end
412
+
413
+ describe "finding the first instance of a model" do
414
+ before(:each) do
415
+ @db = reset_test_db!
416
+ # WithTemplateAndUniqueID.req_design_doc_refresh # Removed by Sam Lown, design doc should be loaded automatically
417
+ WithTemplateAndUniqueID.new('important-field' => '1').save
418
+ WithTemplateAndUniqueID.new('important-field' => '2').save
419
+ WithTemplateAndUniqueID.new('important-field' => '3').save
420
+ WithTemplateAndUniqueID.new('important-field' => '4').save
421
+ end
422
+ it "should make the design doc" do
423
+ WithTemplateAndUniqueID.all
424
+ d = WithTemplateAndUniqueID.design_doc
425
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
426
+ end
427
+ it "should find first" do
428
+ rs = WithTemplateAndUniqueID.first
429
+ rs['important-field'].should == "1"
430
+ end
431
+ it "should return nil if no instances are found" do
432
+ WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
433
+ WithTemplateAndUniqueID.first.should be_nil
434
+ end
435
+ end
436
+
437
+ describe "lazily refreshing the design document" do
438
+ before(:all) do
439
+ @db = reset_test_db!
440
+ WithTemplateAndUniqueID.new('important-field' => '1').save
441
+ end
442
+ it "should not save the design doc twice" do
443
+ WithTemplateAndUniqueID.all
444
+ WithTemplateAndUniqueID.req_design_doc_refresh
445
+ WithTemplateAndUniqueID.refresh_design_doc
446
+ rev = WithTemplateAndUniqueID.design_doc['_rev']
447
+ WithTemplateAndUniqueID.req_design_doc_refresh
448
+ WithTemplateAndUniqueID.refresh_design_doc
449
+ WithTemplateAndUniqueID.design_doc['_rev'].should eql(rev)
450
+ end
451
+ end
452
+
453
+ describe "getting a model with a subobject field" do
454
+ before(:all) do
455
+ course_doc = {
456
+ "title" => "Metaphysics 410",
457
+ "professor" => {
458
+ "name" => ["Mark", "Hinchliff"]
459
+ },
460
+ "ends_at" => "2008/12/19 13:00:00 +0800"
461
+ }
462
+ r = Course.database.save_doc course_doc
463
+ @course = Course.get r['id']
464
+ end
465
+ it "should load the course" do
466
+ @course["professor"]["name"][1].should == "Hinchliff"
467
+ end
468
+ it "should instantiate the professor as a person" do
469
+ @course['professor'].last_name.should == "Hinchliff"
470
+ end
471
+ it "should instantiate the ends_at as a Time" do
472
+ @course['ends_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
473
+ end
474
+ end
475
+
476
+ describe "timestamping" do
477
+ before(:each) do
478
+ oldart = Article.get "saving-this" rescue nil
479
+ oldart.destroy if oldart
480
+ @art = Article.new(:title => "Saving this")
481
+ @art.save
482
+ end
483
+
484
+ it "should define the updated_at and created_at getters and set the values" do
485
+ @obj.save
486
+ obj = WithDefaultValues.get(@obj.id)
487
+ obj.should be_an_instance_of(WithDefaultValues)
488
+ obj.created_at.should be_an_instance_of(Time)
489
+ obj.updated_at.should be_an_instance_of(Time)
490
+ obj.created_at.to_s.should == @obj.updated_at.to_s
491
+ end
492
+
493
+ it "should not change created_at on update" do
494
+ 2.times do
495
+ lambda do
496
+ @art.save
497
+ end.should_not change(@art, :created_at)
498
+ end
499
+ end
500
+
501
+ it "should set the time on create" do
502
+ (Time.now - @art.created_at).should < 2
503
+ foundart = Article.get @art.id
504
+ foundart.created_at.should == foundart.updated_at
505
+ end
506
+ it "should set the time on update" do
507
+ @art.save
508
+ @art.created_at.should < @art.updated_at
509
+ end
510
+ end
511
+
512
+ describe "basic saving and retrieving" do
513
+ it "should work fine" do
514
+ @obj.name = "should be easily saved and retrieved"
515
+ @obj.save
516
+ saved_obj = WithDefaultValues.get(@obj.id)
517
+ saved_obj.should_not be_nil
518
+ end
519
+
520
+ it "should parse the Time attributes automatically" do
521
+ @obj.name = "should parse the Time attributes automatically"
522
+ @obj.set_by_proc.should be_an_instance_of(Time)
523
+ @obj.save
524
+ @obj.set_by_proc.should be_an_instance_of(Time)
525
+ saved_obj = WithDefaultValues.get(@obj.id)
526
+ saved_obj.set_by_proc.should be_an_instance_of(Time)
527
+ end
528
+ end
529
+
530
+ describe "saving a model" do
531
+ before(:all) do
532
+ @sobj = Basic.new
533
+ @sobj.save.should == true
534
+ end
535
+
536
+ it "should save the doc" do
537
+ doc = Basic.get(@sobj.id)
538
+ doc['_id'].should == @sobj.id
539
+ end
540
+
541
+ it "should be set for resaving" do
542
+ rev = @obj.rev
543
+ @sobj['another-key'] = "some value"
544
+ @sobj.save
545
+ @sobj.rev.should_not == rev
546
+ end
547
+
548
+ it "should set the id" do
549
+ @sobj.id.should be_an_instance_of(String)
550
+ end
551
+
552
+ it "should set the type" do
553
+ @sobj[CouchRest.type_field].should == 'Basic'
554
+ end
555
+
556
+ describe "save!" do
557
+
558
+ before(:each) do
559
+ @sobj = Card.new(:first_name => "Marcos", :last_name => "Tapajós")
560
+ end
561
+
562
+ it "should return true if save the document" do
563
+ @sobj.save!.should == true
564
+ end
565
+
566
+ it "should raise error if don't save the document" do
567
+ @sobj.first_name = nil
568
+ lambda { @sobj.save!.should == true }.should raise_error(RuntimeError)
569
+ end
570
+
571
+ end
572
+
573
+
574
+ end
575
+
576
+ describe "saving a model with a unique_id configured" do
577
+ before(:each) do
578
+ @art = Article.new
579
+ @old = Article.database.get('this-is-the-title') rescue nil
580
+ Article.database.delete_doc(@old) if @old
581
+ end
582
+
583
+ it "should be a new document" do
584
+ @art.should be_new
585
+ @art.title.should be_nil
586
+ end
587
+
588
+ it "should require the title" do
589
+ lambda{@art.save}.should raise_error
590
+ @art.title = 'This is the title'
591
+ @art.save.should == true
592
+ end
593
+
594
+ it "should not change the slug on update" do
595
+ @art.title = 'This is the title'
596
+ @art.save.should == true
597
+ @art.title = 'new title'
598
+ @art.save.should == true
599
+ @art.slug.should == 'this-is-the-title'
600
+ end
601
+
602
+ it "should raise an error when the slug is taken" do
603
+ @art.title = 'This is the title'
604
+ @art.save.should == true
605
+ @art2 = Article.new(:title => 'This is the title!')
606
+ lambda{@art2.save}.should raise_error
607
+ end
608
+
609
+ it "should set the slug" do
610
+ @art.title = 'This is the title'
611
+ @art.save.should == true
612
+ @art.slug.should == 'this-is-the-title'
613
+ end
614
+
615
+ it "should set the id" do
616
+ @art.title = 'This is the title'
617
+ @art.save.should == true
618
+ @art.id.should == 'this-is-the-title'
619
+ end
620
+ end
621
+
622
+ describe "saving a model with a unique_id lambda" do
623
+ before(:each) do
624
+ @templated = WithTemplateAndUniqueID.new
625
+ @old = WithTemplateAndUniqueID.get('very-important') rescue nil
626
+ @old.destroy if @old
627
+ end
628
+
629
+ it "should require the field" do
630
+ lambda{@templated.save}.should raise_error
631
+ @templated['important-field'] = 'very-important'
632
+ @templated.save.should == true
633
+ end
634
+
635
+ it "should save with the id" do
636
+ @templated['important-field'] = 'very-important'
637
+ @templated.save.should == true
638
+ t = WithTemplateAndUniqueID.get('very-important')
639
+ t.should == @templated
640
+ end
641
+
642
+ it "should not change the id on update" do
643
+ @templated['important-field'] = 'very-important'
644
+ @templated.save.should == true
645
+ @templated['important-field'] = 'not-important'
646
+ @templated.save.should == true
647
+ t = WithTemplateAndUniqueID.get('very-important')
648
+ t.should == @templated
649
+ end
650
+
651
+ it "should raise an error when the id is taken" do
652
+ @templated['important-field'] = 'very-important'
653
+ @templated.save.should == true
654
+ lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
655
+ end
656
+
657
+ it "should set the id" do
658
+ @templated['important-field'] = 'very-important'
659
+ @templated.save.should == true
660
+ @templated.id.should == 'very-important'
661
+ end
662
+ end
663
+
664
+ describe "destroying an instance" do
665
+ before(:each) do
666
+ @dobj = Basic.new
667
+ @dobj.save.should == true
668
+ end
669
+ it "should return true" do
670
+ result = @dobj.destroy
671
+ result.should == true
672
+ end
673
+ it "should be resavable" do
674
+ @dobj.destroy
675
+ @dobj.rev.should be_nil
676
+ @dobj.id.should be_nil
677
+ @dobj.save.should == true
678
+ end
679
+ it "should make it go away" do
680
+ @dobj.destroy
681
+ lambda{Basic.get!(@dobj.id)}.should raise_error
682
+ end
683
+ end
684
+
685
+
686
+ describe "callbacks" do
687
+
688
+ before(:each) do
689
+ @doc = WithCallBacks.new
690
+ end
691
+
692
+
693
+ describe "validate" do
694
+ it "should run before_validate before validating" do
695
+ @doc.run_before_validate.should be_nil
696
+ @doc.should be_valid
697
+ @doc.run_before_validate.should be_true
698
+ end
699
+ it "should run after_validate after validating" do
700
+ @doc.run_after_validate.should be_nil
701
+ @doc.should be_valid
702
+ @doc.run_after_validate.should be_true
703
+ end
704
+ end
705
+
706
+ describe "save" do
707
+ it "should run the after filter after saving" do
708
+ @doc.run_after_save.should be_nil
709
+ @doc.save.should be_true
710
+ @doc.run_after_save.should be_true
711
+ end
712
+ it "should run the grouped callbacks before saving" do
713
+ @doc.run_one.should be_nil
714
+ @doc.run_two.should be_nil
715
+ @doc.run_three.should be_nil
716
+ @doc.save.should be_true
717
+ @doc.run_one.should be_true
718
+ @doc.run_two.should be_true
719
+ @doc.run_three.should be_true
720
+ end
721
+ it "should not run conditional callbacks" do
722
+ @doc.run_it = false
723
+ @doc.save.should be_true
724
+ @doc.conditional_one.should be_nil
725
+ @doc.conditional_two.should be_nil
726
+ end
727
+ it "should run conditional callbacks" do
728
+ @doc.run_it = true
729
+ @doc.save.should be_true
730
+ @doc.conditional_one.should be_true
731
+ @doc.conditional_two.should be_true
732
+ end
733
+ end
734
+ describe "create" do
735
+ it "should run the before save filter when creating" do
736
+ @doc.run_before_save.should be_nil
737
+ @doc.create.should_not be_nil
738
+ @doc.run_before_save.should be_true
739
+ end
740
+ it "should run the before create filter" do
741
+ @doc.run_before_create.should be_nil
742
+ @doc.create.should_not be_nil
743
+ @doc.create
744
+ @doc.run_before_create.should be_true
745
+ end
746
+ it "should run the after create filter" do
747
+ @doc.run_after_create.should be_nil
748
+ @doc.create.should_not be_nil
749
+ @doc.create
750
+ @doc.run_after_create.should be_true
751
+ end
752
+ end
753
+ describe "update" do
754
+
755
+ before(:each) do
756
+ @doc.save
757
+ end
758
+ it "should run the before update filter when updating an existing document" do
759
+ @doc.run_before_update.should be_nil
760
+ @doc.update
761
+ @doc.run_before_update.should be_true
762
+ end
763
+ it "should run the after update filter when updating an existing document" do
764
+ @doc.run_after_update.should be_nil
765
+ @doc.update
766
+ @doc.run_after_update.should be_true
767
+ end
768
+ it "should run the before update filter when saving an existing document" do
769
+ @doc.run_before_update.should be_nil
770
+ @doc.save
771
+ @doc.run_before_update.should be_true
772
+ end
773
+
774
+ end
775
+ end
776
+
777
+ describe "getter and setter methods" do
778
+ it "should try to call the arg= method before setting :arg in the hash" do
779
+ @doc = WithGetterAndSetterMethods.new(:arg => "foo")
780
+ @doc['arg'].should be_nil
781
+ @doc[:arg].should be_nil
782
+ @doc.other_arg.should == "foo-foo"
783
+ end
784
+ end
785
+
786
+ describe "initialization" do
787
+ it "should call after_initialize method if available" do
788
+ @doc = WithAfterInitializeMethod.new
789
+ @doc['some_value'].should eql('value')
790
+ end
791
+ end
792
+
793
+ describe "recursive validation on an extended document" do
794
+ before :each do
795
+ reset_test_db!
796
+ @cat = Cat.new(:name => 'Sockington')
797
+ end
798
+
799
+ it "should not save if a nested casted model is invalid" do
800
+ @cat.favorite_toy = CatToy.new
801
+ @cat.should_not be_valid
802
+ @cat.save.should be_false
803
+ lambda{@cat.save!}.should raise_error
804
+ end
805
+
806
+ it "should save when nested casted model is valid" do
807
+ @cat.favorite_toy = CatToy.new(:name => 'Squeaky')
808
+ @cat.should be_valid
809
+ @cat.save.should be_true
810
+ lambda{@cat.save!}.should_not raise_error
811
+ end
812
+
813
+ it "should not save when nested collection contains an invalid casted model" do
814
+ @cat.toys = [CatToy.new(:name => 'Feather'), CatToy.new]
815
+ @cat.should_not be_valid
816
+ @cat.save.should be_false
817
+ lambda{@cat.save!}.should raise_error
818
+ end
819
+
820
+ it "should save when nested collection contains valid casted models" do
821
+ @cat.toys = [CatToy.new(:name => 'feather'), CatToy.new(:name => 'ball-o-twine')]
822
+ @cat.should be_valid
823
+ @cat.save.should be_true
824
+ lambda{@cat.save!}.should_not raise_error
825
+ end
826
+
827
+ it "should not fail if the nested casted model doesn't have validation" do
828
+ Cat.property :trainer, :cast_as => 'Person'
829
+ Cat.validates_presence_of :name
830
+ cat = Cat.new(:name => 'Mr Bigglesworth')
831
+ cat.trainer = Person.new
832
+ cat.trainer.validatable?.should be_false
833
+ cat.should be_valid
834
+ cat.save.should be_true
835
+ end
836
+ end
837
+
838
+ describe "searching the contents of an extended document" do
839
+ before :each do
840
+ @db = reset_test_db!
841
+
842
+ names = ["Fuzzy", "Whiskers", "Mr Bigglesworth", "Sockington", "Smitty", "Sammy", "Samson", "Simon"]
843
+ names.each { |name| Cat.create(:name => name) }
844
+
845
+ search_function = { 'defaults' => {'store' => 'no', 'index' => 'analyzed_no_norms'},
846
+ 'index' => "function(doc) { ret = new Document(); ret.add(doc['name'], {'field':'name'}); return ret; }" }
847
+ @db.save_doc({'_id' => '_design/search', 'fulltext' => {'cats' => search_function}})
848
+ end
849
+
850
+ it "should be able to paginate through a large set of search results" do
851
+ if couchdb_lucene_available?
852
+ names = []
853
+ Cat.paginated_each(:design_doc => "search", :view_name => "cats",
854
+ :q => 'name:S*', :search => true, :include_docs => true, :per_page => 3) do |cat|
855
+ cat.should_not be_nil
856
+ names << cat.name
857
+ end
858
+
859
+ names.size.should == 5
860
+ names.should include('Sockington')
861
+ names.should include('Smitty')
862
+ names.should include('Sammy')
863
+ names.should include('Samson')
864
+ names.should include('Simon')
865
+ end
866
+ end
867
+ end
868
+
869
+ end