jpbougie-couchrest 0.27

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