peterpunk-couchrest 0.23.1

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 (94) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +95 -0
  3. data/Rakefile +75 -0
  4. data/THANKS.md +18 -0
  5. data/examples/model/example.rb +144 -0
  6. data/examples/word_count/markov +38 -0
  7. data/examples/word_count/views/books/chunked-map.js +3 -0
  8. data/examples/word_count/views/books/united-map.js +1 -0
  9. data/examples/word_count/views/markov/chain-map.js +6 -0
  10. data/examples/word_count/views/markov/chain-reduce.js +7 -0
  11. data/examples/word_count/views/word_count/count-map.js +6 -0
  12. data/examples/word_count/views/word_count/count-reduce.js +3 -0
  13. data/examples/word_count/word_count.rb +46 -0
  14. data/examples/word_count/word_count_query.rb +40 -0
  15. data/examples/word_count/word_count_views.rb +26 -0
  16. data/lib/couchrest.rb +198 -0
  17. data/lib/couchrest/commands/generate.rb +71 -0
  18. data/lib/couchrest/commands/push.rb +103 -0
  19. data/lib/couchrest/core/database.rb +303 -0
  20. data/lib/couchrest/core/design.rb +79 -0
  21. data/lib/couchrest/core/document.rb +87 -0
  22. data/lib/couchrest/core/response.rb +20 -0
  23. data/lib/couchrest/core/server.rb +88 -0
  24. data/lib/couchrest/core/view.rb +4 -0
  25. data/lib/couchrest/helper/pager.rb +103 -0
  26. data/lib/couchrest/helper/streamer.rb +44 -0
  27. data/lib/couchrest/helper/upgrade.rb +51 -0
  28. data/lib/couchrest/mixins.rb +4 -0
  29. data/lib/couchrest/mixins/attachments.rb +31 -0
  30. data/lib/couchrest/mixins/callbacks.rb +483 -0
  31. data/lib/couchrest/mixins/class_proxy.rb +108 -0
  32. data/lib/couchrest/mixins/design_doc.rb +90 -0
  33. data/lib/couchrest/mixins/document_queries.rb +44 -0
  34. data/lib/couchrest/mixins/extended_attachments.rb +68 -0
  35. data/lib/couchrest/mixins/extended_document_mixins.rb +7 -0
  36. data/lib/couchrest/mixins/properties.rb +133 -0
  37. data/lib/couchrest/mixins/validation.rb +244 -0
  38. data/lib/couchrest/mixins/views.rb +169 -0
  39. data/lib/couchrest/monkeypatches.rb +113 -0
  40. data/lib/couchrest/more/casted_model.rb +28 -0
  41. data/lib/couchrest/more/extended_document.rb +219 -0
  42. data/lib/couchrest/more/property.rb +40 -0
  43. data/lib/couchrest/support/blank.rb +42 -0
  44. data/lib/couchrest/support/class.rb +176 -0
  45. data/lib/couchrest/validation/auto_validate.rb +163 -0
  46. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  47. data/lib/couchrest/validation/validation_errors.rb +119 -0
  48. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  49. data/lib/couchrest/validation/validators/confirmation_validator.rb +99 -0
  50. data/lib/couchrest/validation/validators/format_validator.rb +117 -0
  51. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  52. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  53. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  54. data/lib/couchrest/validation/validators/length_validator.rb +134 -0
  55. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  56. data/lib/couchrest/validation/validators/numeric_validator.rb +104 -0
  57. data/lib/couchrest/validation/validators/required_field_validator.rb +109 -0
  58. data/lib/couchrest/validation/validators/uniqueness_validator.rb +89 -0
  59. data/spec/couchrest/core/couchrest_spec.rb +201 -0
  60. data/spec/couchrest/core/database_spec.rb +699 -0
  61. data/spec/couchrest/core/design_spec.rb +138 -0
  62. data/spec/couchrest/core/document_spec.rb +267 -0
  63. data/spec/couchrest/core/server_spec.rb +35 -0
  64. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  65. data/spec/couchrest/helpers/streamer_spec.rb +23 -0
  66. data/spec/couchrest/more/casted_extended_doc_spec.rb +40 -0
  67. data/spec/couchrest/more/casted_model_spec.rb +98 -0
  68. data/spec/couchrest/more/extended_doc_attachment_spec.rb +130 -0
  69. data/spec/couchrest/more/extended_doc_spec.rb +531 -0
  70. data/spec/couchrest/more/extended_doc_subclass_spec.rb +98 -0
  71. data/spec/couchrest/more/extended_doc_view_spec.rb +355 -0
  72. data/spec/couchrest/more/property_spec.rb +190 -0
  73. data/spec/fixtures/attachments/README +3 -0
  74. data/spec/fixtures/attachments/couchdb.png +0 -0
  75. data/spec/fixtures/attachments/test.html +11 -0
  76. data/spec/fixtures/more/article.rb +34 -0
  77. data/spec/fixtures/more/card.rb +20 -0
  78. data/spec/fixtures/more/course.rb +14 -0
  79. data/spec/fixtures/more/event.rb +6 -0
  80. data/spec/fixtures/more/invoice.rb +17 -0
  81. data/spec/fixtures/more/location.rb +18 -0
  82. data/spec/fixtures/more/person.rb +8 -0
  83. data/spec/fixtures/more/question.rb +6 -0
  84. data/spec/fixtures/more/service.rb +12 -0
  85. data/spec/fixtures/views/lib.js +3 -0
  86. data/spec/fixtures/views/test_view/lib.js +3 -0
  87. data/spec/fixtures/views/test_view/only-map.js +4 -0
  88. data/spec/fixtures/views/test_view/test-map.js +3 -0
  89. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  90. data/spec/spec.opts +6 -0
  91. data/spec/spec_helper.rb +26 -0
  92. data/utils/remap.rb +27 -0
  93. data/utils/subset.rb +30 -0
  94. metadata +203 -0
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe CouchRest::Streamer do
4
+ before(:all) do
5
+ @cr = CouchRest.new(COUCHHOST)
6
+ @db = @cr.database(TESTDB)
7
+ @db.delete! rescue nil
8
+ @db = @cr.create_db(TESTDB) rescue nil
9
+ @streamer = CouchRest::Streamer.new(@db)
10
+ @docs = (1..1000).collect{|i| {:integer => i, :string => i.to_s}}
11
+ @db.bulk_save(@docs)
12
+ end
13
+
14
+ it "should yield each row in a view" do
15
+ count = 0
16
+ sum = 0
17
+ @streamer.view("_all_docs") do |row|
18
+ count += 1
19
+ end
20
+ count.should == 1001
21
+ end
22
+
23
+ end
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+ require File.join(FIXTURE_PATH, 'more', 'card')
3
+
4
+ class Car < CouchRest::ExtendedDocument
5
+ use_database TEST_SERVER.default_database
6
+
7
+ property :name
8
+ property :driver, :cast_as => 'Driver'
9
+ end
10
+
11
+ class Driver < CouchRest::ExtendedDocument
12
+ use_database TEST_SERVER.default_database
13
+ # You have to add a casted_by accessor if you want to reach a casted extended doc parent
14
+ attr_accessor :casted_by
15
+
16
+ property :name
17
+ end
18
+
19
+ describe "casting an extended document" do
20
+
21
+ before(:each) do
22
+ @car = Car.new(:name => 'Renault 306')
23
+ @driver = Driver.new(:name => 'Matt')
24
+ end
25
+
26
+ # it "should not create an empty casted object" do
27
+ # @car.driver.should be_nil
28
+ # end
29
+
30
+ it "should let you assign the casted attribute after instantializing an object" do
31
+ @car.driver = @driver
32
+ @car.driver.name.should == 'Matt'
33
+ end
34
+
35
+ it "should let the casted document who casted it" do
36
+ Car.new(:name => 'Renault 306', :driver => @driver)
37
+ @car.driver.casted_by.should == @car
38
+ end
39
+
40
+ end
@@ -0,0 +1,98 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+ require File.join(FIXTURE_PATH, 'more', 'card')
3
+
4
+ class WithCastedModelMixin < Hash
5
+ include CouchRest::CastedModel
6
+ property :name
7
+ end
8
+
9
+ class DummyModel < CouchRest::ExtendedDocument
10
+ use_database TEST_SERVER.default_database
11
+ raise "Default DB not set" if TEST_SERVER.default_database.nil?
12
+ property :casted_attribute, :cast_as => 'WithCastedModelMixin'
13
+ property :keywords, :cast_as => ["String"]
14
+ end
15
+
16
+ describe CouchRest::CastedModel do
17
+
18
+ describe "A non hash class including CastedModel" do
19
+ it "should fail raising and include error" do
20
+ lambda do
21
+ class NotAHashButWithCastedModelMixin
22
+ include CouchRest::CastedModel
23
+ property :name
24
+ end
25
+
26
+ end.should raise_error
27
+ end
28
+ end
29
+
30
+ describe "isolated" do
31
+ before(:each) do
32
+ @obj = WithCastedModelMixin.new
33
+ end
34
+ it "should automatically include the property mixin and define getters and setters" do
35
+ @obj.name = 'Matt'
36
+ @obj.name.should == 'Matt'
37
+ end
38
+ end
39
+
40
+ describe "casted as attribute" do
41
+ before(:each) do
42
+ @obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
43
+ @casted_obj = @obj.casted_attribute
44
+ end
45
+
46
+ it "should be available from its parent" do
47
+ @casted_obj.should be_an_instance_of(WithCastedModelMixin)
48
+ end
49
+
50
+ it "should have the getters defined" do
51
+ @casted_obj.name.should == 'whatever'
52
+ end
53
+
54
+ it "should know who casted it" do
55
+ @casted_obj.casted_by.should == @obj
56
+ end
57
+ end
58
+
59
+ describe "casted as an array of a different type" do
60
+ before(:each) do
61
+ @obj = DummyModel.new(:keywords => ['couch', 'sofa', 'relax', 'canapé'])
62
+ end
63
+
64
+ it "should cast the array propery" do
65
+ @obj.keywords.should be_an_instance_of(Array)
66
+ @obj.keywords.first.should == 'couch'
67
+ end
68
+
69
+ end
70
+
71
+ describe "saved document with casted models" do
72
+ before(:each) do
73
+ reset_test_db!
74
+ @obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
75
+ @obj.save.should be_true
76
+ @obj = DummyModel.get(@obj.id)
77
+ end
78
+
79
+ it "should be able to load with the casted models" do
80
+ casted_obj = @obj.casted_attribute
81
+ casted_obj.should_not be_nil
82
+ casted_obj.should be_an_instance_of(WithCastedModelMixin)
83
+ end
84
+
85
+ it "should have defined getters for the casted model" do
86
+ casted_obj = @obj.casted_attribute
87
+ casted_obj.name.should == "whatever"
88
+ end
89
+
90
+ it "should have defined setters for the casted model" do
91
+ casted_obj = @obj.casted_attribute
92
+ casted_obj.name = "test"
93
+ casted_obj.name.should == "test"
94
+ end
95
+
96
+ end
97
+
98
+ end
@@ -0,0 +1,130 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
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
+ end
72
+
73
+ describe 'reading, updating, and deleting an attachment' do
74
+ before(:each) do
75
+ @obj = Basic.new
76
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
77
+ @attachment_name = 'my_attachment'
78
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
79
+ @obj.save.should == true
80
+ @file.rewind
81
+ @content_type = 'media/mp3'
82
+ end
83
+
84
+ it 'should read an attachment that exists' do
85
+ @obj.read_attachment(@attachment_name).should == @file.read
86
+ end
87
+
88
+ it 'should update an attachment that exists' do
89
+ file = File.open(FIXTURE_PATH + '/attachments/README')
90
+ @file.should_not == file
91
+ @obj.update_attachment(:file => file, :name => @attachment_name)
92
+ @obj.save
93
+ reloaded_obj = Basic.get(@obj.id)
94
+ file.rewind
95
+ reloaded_obj.read_attachment(@attachment_name).should_not == @file.read
96
+ reloaded_obj.read_attachment(@attachment_name).should == file.read
97
+ end
98
+
99
+ it 'should se the content-type if passed' do
100
+ file = File.open(FIXTURE_PATH + '/attachments/README')
101
+ @file.should_not == file
102
+ @obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
103
+ @obj['_attachments'][@attachment_name]['content-type'].should == @content_type
104
+ end
105
+
106
+ it 'should delete an attachment that exists' do
107
+ @obj.delete_attachment(@attachment_name)
108
+ @obj.save
109
+ lambda{Basic.get(@obj.id).read_attachment(@attachment_name)}.should raise_error
110
+ end
111
+ end
112
+
113
+ describe "#attachment_url" do
114
+ before(:each) do
115
+ @obj = Basic.new
116
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
117
+ @attachment_name = 'my_attachment'
118
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
119
+ @obj.save.should == true
120
+ end
121
+
122
+ it 'should return nil if attachment does not exist' do
123
+ @obj.attachment_url('bogus').should be_nil
124
+ end
125
+
126
+ it 'should return the attachment URL as specified by CouchDB HttpDocumentApi' do
127
+ @obj.attachment_url(@attachment_name).should == "#{Basic.database}/#{@obj.id}/#{@attachment_name}"
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,531 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+ require File.join(FIXTURE_PATH, 'more', 'article')
3
+ require File.join(FIXTURE_PATH, 'more', 'course')
4
+
5
+
6
+ describe "ExtendedDocument" do
7
+
8
+ class WithDefaultValues < CouchRest::ExtendedDocument
9
+ use_database TEST_SERVER.default_database
10
+ property :preset, :default => {:right => 10, :top_align => false}
11
+ property :set_by_proc, :default => Proc.new{Time.now}, :cast_as => 'Time'
12
+ property :tags, :default => []
13
+ property :name
14
+ timestamps!
15
+ end
16
+
17
+ class WithCallBacks < CouchRest::ExtendedDocument
18
+ include CouchRest::Validation
19
+
20
+ use_database TEST_SERVER.default_database
21
+ property :name
22
+ property :run_before_validate
23
+ property :run_after_validate
24
+ property :run_before_save
25
+ property :run_after_save
26
+ property :run_before_create
27
+ property :run_after_create
28
+ property :run_before_update
29
+ property :run_after_update
30
+
31
+ save_callback :before do |object|
32
+ object.run_before_save = true
33
+ end
34
+ save_callback :after do |object|
35
+ object.run_after_save = true
36
+ end
37
+
38
+ validate_callback :before do |object|
39
+ object.run_before_validate = true
40
+ end
41
+ validate_callback :after do |object|
42
+ object.run_after_validate = true
43
+ end
44
+
45
+ create_callback :before do |object|
46
+ object.run_before_create = true
47
+ end
48
+ create_callback :after do |object|
49
+ object.run_after_create = true
50
+ end
51
+ update_callback :before do |object|
52
+ object.run_before_update = true
53
+ end
54
+ update_callback :after do |object|
55
+ object.run_after_update = true
56
+ end
57
+ end
58
+
59
+ class WithTemplateAndUniqueID < CouchRest::ExtendedDocument
60
+ use_database TEST_SERVER.default_database
61
+ unique_id do |model|
62
+ model['important-field']
63
+ end
64
+ property :preset, :default => 'value'
65
+ property :has_no_default
66
+ end
67
+
68
+ before(:each) do
69
+ @obj = WithDefaultValues.new
70
+ end
71
+
72
+ describe "instance database connection" do
73
+ it "should use the default database" do
74
+ @obj.database.name.should == 'couchrest-test'
75
+ end
76
+
77
+ it "should override the default db" do
78
+ @obj.database = TEST_SERVER.database!('couchrest-extendedmodel-test')
79
+ @obj.database.name.should == 'couchrest-extendedmodel-test'
80
+ @obj.database.delete!
81
+ end
82
+ end
83
+
84
+ describe "a new model" do
85
+ it "should be a new_record" do
86
+ @obj = Basic.new
87
+ @obj.rev.should be_nil
88
+ @obj.should be_a_new_record
89
+ end
90
+ it "should be a new_document" do
91
+ @obj = Basic.new
92
+ @obj.rev.should be_nil
93
+ @obj.should be_a_new_document
94
+ end
95
+ end
96
+
97
+ describe "update attributes without saving" do
98
+ before(:each) do
99
+ a = Article.get "big-bad-danger" rescue nil
100
+ a.destroy if a
101
+ @art = Article.new(:title => "big bad danger")
102
+ @art.save
103
+ end
104
+ it "should work for attribute= methods" do
105
+ @art['title'].should == "big bad danger"
106
+ @art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
107
+ @art['title'].should == "super danger"
108
+ end
109
+
110
+ it "should flip out if an attribute= method is missing" do
111
+ lambda {
112
+ @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
113
+ }.should raise_error
114
+ end
115
+
116
+ it "should not change other attributes if there is an error" do
117
+ lambda {
118
+ @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
119
+ }.should raise_error
120
+ @art['title'].should == "big bad danger"
121
+ end
122
+ end
123
+
124
+ describe "update attributes" do
125
+ before(:each) do
126
+ a = Article.get "big-bad-danger" rescue nil
127
+ a.destroy if a
128
+ @art = Article.new(:title => "big bad danger")
129
+ @art.save
130
+ end
131
+ it "should save" do
132
+ @art['title'].should == "big bad danger"
133
+ @art.update_attributes('date' => Time.now, :title => "super danger")
134
+ loaded = Article.get(@art.id)
135
+ loaded['title'].should == "super danger"
136
+ end
137
+ end
138
+
139
+ describe "with default" do
140
+ it "should have the default value set at initalization" do
141
+ @obj.preset.should == {:right => 10, :top_align => false}
142
+ end
143
+
144
+ it "should automatically call a proc default at initialization" do
145
+ @obj.set_by_proc.should be_an_instance_of(Time)
146
+ @obj.set_by_proc.should == @obj.set_by_proc
147
+ @obj.set_by_proc.should < Time.now
148
+ end
149
+
150
+ it "should let you overwrite the default values" do
151
+ obj = WithDefaultValues.new(:preset => 'test')
152
+ obj.preset = 'test'
153
+ end
154
+
155
+ it "should work with a default empty array" do
156
+ obj = WithDefaultValues.new(:tags => ['spec'])
157
+ obj.tags.should == ['spec']
158
+ end
159
+ end
160
+
161
+ describe "a doc with template values (CR::Model spec)" do
162
+ before(:all) do
163
+ WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
164
+ WithTemplateAndUniqueID.database.bulk_delete
165
+ @tmpl = WithTemplateAndUniqueID.new
166
+ @tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1')
167
+ end
168
+ it "should have fields set when new" do
169
+ @tmpl.preset.should == 'value'
170
+ end
171
+ it "shouldn't override explicitly set values" do
172
+ @tmpl2.preset.should == 'not_value'
173
+ end
174
+ it "shouldn't override existing documents" do
175
+ @tmpl2.save
176
+ tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
177
+ @tmpl2.preset.should == 'not_value'
178
+ tmpl2_reloaded.preset.should == 'not_value'
179
+ end
180
+ end
181
+
182
+ describe "getting a model" do
183
+ before(:all) do
184
+ @art = Article.new(:title => 'All About Getting')
185
+ @art.save
186
+ end
187
+ it "should load and instantiate it" do
188
+ foundart = Article.get @art.id
189
+ foundart.title.should == "All About Getting"
190
+ end
191
+ end
192
+
193
+ describe "getting a model with a subobjects array" do
194
+ before(:all) do
195
+ course_doc = {
196
+ "title" => "Metaphysics 200",
197
+ "questions" => [
198
+ {
199
+ "q" => "Carve the ___ of reality at the ___.",
200
+ "a" => ["beast","joints"]
201
+ },{
202
+ "q" => "Who layed the smack down on Leibniz's Law?",
203
+ "a" => "Willard Van Orman Quine"
204
+ }
205
+ ]
206
+ }
207
+ r = Course.database.save_doc course_doc
208
+ @course = Course.get r['id']
209
+ end
210
+ it "should load the course" do
211
+ @course.title.should == "Metaphysics 200"
212
+ end
213
+ it "should instantiate them as such" do
214
+ @course["questions"][0].a[0].should == "beast"
215
+ end
216
+ end
217
+
218
+ describe "finding all instances of a model" do
219
+ before(:all) do
220
+ WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
221
+ WithTemplateAndUniqueID.database.bulk_delete
222
+ WithTemplateAndUniqueID.new('important-field' => '1').save
223
+ WithTemplateAndUniqueID.new('important-field' => '2').save
224
+ WithTemplateAndUniqueID.new('important-field' => '3').save
225
+ WithTemplateAndUniqueID.new('important-field' => '4').save
226
+ end
227
+ it "should make the design doc" do
228
+ WithTemplateAndUniqueID.all
229
+ d = WithTemplateAndUniqueID.design_doc
230
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
231
+ end
232
+ it "should find all" do
233
+ rs = WithTemplateAndUniqueID.all
234
+ rs.length.should == 4
235
+ end
236
+ end
237
+
238
+ describe "finding the first instance of a model" do
239
+ before(:each) do
240
+ @db = reset_test_db!
241
+ WithTemplateAndUniqueID.new('important-field' => '1').save
242
+ WithTemplateAndUniqueID.new('important-field' => '2').save
243
+ WithTemplateAndUniqueID.new('important-field' => '3').save
244
+ WithTemplateAndUniqueID.new('important-field' => '4').save
245
+ end
246
+ it "should make the design doc" do
247
+ WithTemplateAndUniqueID.all
248
+ d = WithTemplateAndUniqueID.design_doc
249
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
250
+ end
251
+ it "should find first" do
252
+ rs = WithTemplateAndUniqueID.first
253
+ rs['important-field'].should == "1"
254
+ end
255
+ it "should return nil if no instances are found" do
256
+ WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
257
+ WithTemplateAndUniqueID.first.should be_nil
258
+ end
259
+ end
260
+
261
+ describe "getting a model with a subobject field" do
262
+ before(:all) do
263
+ course_doc = {
264
+ "title" => "Metaphysics 410",
265
+ "professor" => {
266
+ "name" => ["Mark", "Hinchliff"]
267
+ },
268
+ "final_test_at" => "2008/12/19 13:00:00 +0800"
269
+ }
270
+ r = Course.database.save_doc course_doc
271
+ @course = Course.get r['id']
272
+ end
273
+ it "should load the course" do
274
+ @course["professor"]["name"][1].should == "Hinchliff"
275
+ end
276
+ it "should instantiate the professor as a person" do
277
+ @course['professor'].last_name.should == "Hinchliff"
278
+ end
279
+ it "should instantiate the final_test_at as a Time" do
280
+ @course['final_test_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
281
+ end
282
+ end
283
+
284
+ describe "timestamping" do
285
+ before(:each) do
286
+ oldart = Article.get "saving-this" rescue nil
287
+ oldart.destroy if oldart
288
+ @art = Article.new(:title => "Saving this")
289
+ @art.save
290
+ end
291
+
292
+ it "should define the updated_at and created_at getters and set the values" do
293
+ @obj.save
294
+ obj = WithDefaultValues.get(@obj.id)
295
+ obj.should be_an_instance_of(WithDefaultValues)
296
+ obj.created_at.should be_an_instance_of(Time)
297
+ obj.updated_at.should be_an_instance_of(Time)
298
+ obj.created_at.to_s.should == @obj.updated_at.to_s
299
+ end
300
+ it "should set the time on create" do
301
+ (Time.now - @art.created_at).should < 2
302
+ foundart = Article.get @art.id
303
+ foundart.created_at.should == foundart.updated_at
304
+ end
305
+ it "should set the time on update" do
306
+ @art.save
307
+ @art.created_at.should < @art.updated_at
308
+ end
309
+ end
310
+
311
+ describe "basic saving and retrieving" do
312
+ it "should work fine" do
313
+ @obj.name = "should be easily saved and retrieved"
314
+ @obj.save
315
+ saved_obj = WithDefaultValues.get(@obj.id)
316
+ saved_obj.should_not be_nil
317
+ end
318
+
319
+ it "should parse the Time attributes automatically" do
320
+ @obj.name = "should parse the Time attributes automatically"
321
+ @obj.set_by_proc.should be_an_instance_of(Time)
322
+ @obj.save
323
+ @obj.set_by_proc.should be_an_instance_of(Time)
324
+ saved_obj = WithDefaultValues.get(@obj.id)
325
+ saved_obj.set_by_proc.should be_an_instance_of(Time)
326
+ end
327
+ end
328
+
329
+ describe "saving a model" do
330
+ before(:all) do
331
+ @sobj = Basic.new
332
+ @sobj.save.should == true
333
+ end
334
+
335
+ it "should save the doc" do
336
+ doc = Basic.get(@sobj.id)
337
+ doc['_id'].should == @sobj.id
338
+ end
339
+
340
+ it "should be set for resaving" do
341
+ rev = @obj.rev
342
+ @sobj['another-key'] = "some value"
343
+ @sobj.save
344
+ @sobj.rev.should_not == rev
345
+ end
346
+
347
+ it "should set the id" do
348
+ @sobj.id.should be_an_instance_of(String)
349
+ end
350
+
351
+ it "should set the type" do
352
+ @sobj['couchrest-type'].should == 'Basic'
353
+ end
354
+ end
355
+
356
+ describe "saving a model with a unique_id configured" do
357
+ before(:each) do
358
+ @art = Article.new
359
+ @old = Article.database.get('this-is-the-title') rescue nil
360
+ Article.database.delete_doc(@old) if @old
361
+ end
362
+
363
+ it "should be a new document" do
364
+ @art.should be_a_new_document
365
+ @art.title.should be_nil
366
+ end
367
+
368
+ it "should require the title" do
369
+ lambda{@art.save}.should raise_error
370
+ @art.title = 'This is the title'
371
+ @art.save.should == true
372
+ end
373
+
374
+ it "should not change the slug on update" do
375
+ @art.title = 'This is the title'
376
+ @art.save.should == true
377
+ @art.title = 'new title'
378
+ @art.save.should == true
379
+ @art.slug.should == 'this-is-the-title'
380
+ end
381
+
382
+ it "should raise an error when the slug is taken" do
383
+ @art.title = 'This is the title'
384
+ @art.save.should == true
385
+ @art2 = Article.new(:title => 'This is the title!')
386
+ lambda{@art2.save}.should raise_error
387
+ end
388
+
389
+ it "should set the slug" do
390
+ @art.title = 'This is the title'
391
+ @art.save.should == true
392
+ @art.slug.should == 'this-is-the-title'
393
+ end
394
+
395
+ it "should set the id" do
396
+ @art.title = 'This is the title'
397
+ @art.save.should == true
398
+ @art.id.should == 'this-is-the-title'
399
+ end
400
+ end
401
+
402
+ describe "saving a model with a unique_id lambda" do
403
+ before(:each) do
404
+ @templated = WithTemplateAndUniqueID.new
405
+ @old = WithTemplateAndUniqueID.get('very-important') rescue nil
406
+ @old.destroy if @old
407
+ end
408
+
409
+ it "should require the field" do
410
+ lambda{@templated.save}.should raise_error
411
+ @templated['important-field'] = 'very-important'
412
+ @templated.save.should == true
413
+ end
414
+
415
+ it "should save with the id" do
416
+ @templated['important-field'] = 'very-important'
417
+ @templated.save.should == true
418
+ t = WithTemplateAndUniqueID.get('very-important')
419
+ t.should == @templated
420
+ end
421
+
422
+ it "should not change the id on update" do
423
+ @templated['important-field'] = 'very-important'
424
+ @templated.save.should == true
425
+ @templated['important-field'] = 'not-important'
426
+ @templated.save.should == true
427
+ t = WithTemplateAndUniqueID.get('very-important')
428
+ t.should == @templated
429
+ end
430
+
431
+ it "should raise an error when the id is taken" do
432
+ @templated['important-field'] = 'very-important'
433
+ @templated.save.should == true
434
+ lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
435
+ end
436
+
437
+ it "should set the id" do
438
+ @templated['important-field'] = 'very-important'
439
+ @templated.save.should == true
440
+ @templated.id.should == 'very-important'
441
+ end
442
+ end
443
+
444
+ describe "destroying an instance" do
445
+ before(:each) do
446
+ @dobj = Basic.new
447
+ @dobj.save.should == true
448
+ end
449
+ it "should return true" do
450
+ result = @dobj.destroy
451
+ result.should == true
452
+ end
453
+ it "should be resavable" do
454
+ @dobj.destroy
455
+ @dobj.rev.should be_nil
456
+ @dobj.id.should be_nil
457
+ @dobj.save.should == true
458
+ end
459
+ it "should make it go away" do
460
+ @dobj.destroy
461
+ lambda{Basic.get(@dobj.id)}.should raise_error
462
+ end
463
+ end
464
+
465
+
466
+ describe "callbacks" do
467
+
468
+ before(:each) do
469
+ @doc = WithCallBacks.new
470
+ end
471
+
472
+ describe "validate" do
473
+ it "should run the after filter after validating" do
474
+ @doc.run_after_validate.should be_nil
475
+ @doc.valid?.should be_true
476
+ @doc.run_after_validate.should be_true
477
+ end
478
+ end
479
+
480
+
481
+ describe "save" do
482
+ it "should run the after filter after saving" do
483
+ @doc.run_after_save.should be_nil
484
+ @doc.save.should be_true
485
+ @doc.run_after_save.should be_true
486
+ end
487
+ end
488
+
489
+ describe "create" do
490
+ it "should run the before save filter when creating" do
491
+ @doc.run_before_save.should be_nil
492
+ @doc.create.should_not be_nil
493
+ @doc.run_before_save.should be_true
494
+ end
495
+ it "should run the before create filter" do
496
+ @doc.run_before_create.should be_nil
497
+ @doc.create.should_not be_nil
498
+ @doc.create
499
+ @doc.run_before_create.should be_true
500
+ end
501
+ it "should run the after create filter" do
502
+ @doc.run_after_create.should be_nil
503
+ @doc.create.should_not be_nil
504
+ @doc.create
505
+ @doc.run_after_create.should be_true
506
+ end
507
+ end
508
+ describe "update" do
509
+
510
+ before(:each) do
511
+ @doc.save
512
+ end
513
+ it "should run the before update filter when updating an existing document" do
514
+ @doc.run_before_update.should be_nil
515
+ @doc.update
516
+ @doc.run_before_update.should be_true
517
+ end
518
+ it "should run the after update filter when updating an existing document" do
519
+ @doc.run_after_update.should be_nil
520
+ @doc.update
521
+ @doc.run_after_update.should be_true
522
+ end
523
+ it "should run the before update filter when saving an existing document" do
524
+ @doc.run_before_update.should be_nil
525
+ @doc.save
526
+ @doc.run_before_update.should be_true
527
+ end
528
+
529
+ end
530
+ end
531
+ end