oz-couchrest 0.29

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