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