will-couchrest 0.32.1

Sign up to get free protection for your applications and to get access to all the features.
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