openlogic-couchrest_model 1.0.0

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 (107) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +4 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +176 -0
  5. data/README.md +137 -0
  6. data/Rakefile +38 -0
  7. data/THANKS.md +21 -0
  8. data/VERSION +1 -0
  9. data/benchmarks/dirty.rb +118 -0
  10. data/couchrest_model.gemspec +36 -0
  11. data/history.md +309 -0
  12. data/init.rb +1 -0
  13. data/lib/couchrest/model.rb +10 -0
  14. data/lib/couchrest/model/associations.rb +231 -0
  15. data/lib/couchrest/model/base.rb +129 -0
  16. data/lib/couchrest/model/callbacks.rb +28 -0
  17. data/lib/couchrest/model/casted_array.rb +83 -0
  18. data/lib/couchrest/model/casted_by.rb +33 -0
  19. data/lib/couchrest/model/casted_hash.rb +84 -0
  20. data/lib/couchrest/model/class_proxy.rb +135 -0
  21. data/lib/couchrest/model/collection.rb +273 -0
  22. data/lib/couchrest/model/configuration.rb +67 -0
  23. data/lib/couchrest/model/connection.rb +70 -0
  24. data/lib/couchrest/model/core_extensions/hash.rb +9 -0
  25. data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
  26. data/lib/couchrest/model/design_doc.rb +128 -0
  27. data/lib/couchrest/model/designs.rb +91 -0
  28. data/lib/couchrest/model/designs/view.rb +513 -0
  29. data/lib/couchrest/model/dirty.rb +39 -0
  30. data/lib/couchrest/model/document_queries.rb +99 -0
  31. data/lib/couchrest/model/embeddable.rb +78 -0
  32. data/lib/couchrest/model/errors.rb +25 -0
  33. data/lib/couchrest/model/extended_attachments.rb +83 -0
  34. data/lib/couchrest/model/persistence.rb +178 -0
  35. data/lib/couchrest/model/properties.rb +228 -0
  36. data/lib/couchrest/model/property.rb +114 -0
  37. data/lib/couchrest/model/property_protection.rb +71 -0
  38. data/lib/couchrest/model/proxyable.rb +183 -0
  39. data/lib/couchrest/model/support/couchrest_database.rb +13 -0
  40. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  41. data/lib/couchrest/model/typecast.rb +154 -0
  42. data/lib/couchrest/model/validations.rb +80 -0
  43. data/lib/couchrest/model/validations/casted_model.rb +16 -0
  44. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  45. data/lib/couchrest/model/validations/uniqueness.rb +69 -0
  46. data/lib/couchrest/model/views.rb +151 -0
  47. data/lib/couchrest/railtie.rb +24 -0
  48. data/lib/couchrest_model.rb +66 -0
  49. data/lib/rails/generators/couchrest_model.rb +16 -0
  50. data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
  51. data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
  52. data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
  53. data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
  54. data/spec/.gitignore +1 -0
  55. data/spec/fixtures/attachments/README +3 -0
  56. data/spec/fixtures/attachments/couchdb.png +0 -0
  57. data/spec/fixtures/attachments/test.html +11 -0
  58. data/spec/fixtures/config/couchdb.yml +10 -0
  59. data/spec/fixtures/models/article.rb +36 -0
  60. data/spec/fixtures/models/base.rb +164 -0
  61. data/spec/fixtures/models/card.rb +19 -0
  62. data/spec/fixtures/models/cat.rb +23 -0
  63. data/spec/fixtures/models/client.rb +6 -0
  64. data/spec/fixtures/models/course.rb +27 -0
  65. data/spec/fixtures/models/event.rb +8 -0
  66. data/spec/fixtures/models/invoice.rb +14 -0
  67. data/spec/fixtures/models/key_chain.rb +5 -0
  68. data/spec/fixtures/models/membership.rb +4 -0
  69. data/spec/fixtures/models/person.rb +11 -0
  70. data/spec/fixtures/models/project.rb +6 -0
  71. data/spec/fixtures/models/question.rb +7 -0
  72. data/spec/fixtures/models/sale_entry.rb +9 -0
  73. data/spec/fixtures/models/sale_invoice.rb +14 -0
  74. data/spec/fixtures/models/service.rb +10 -0
  75. data/spec/fixtures/models/user.rb +22 -0
  76. data/spec/fixtures/views/lib.js +3 -0
  77. data/spec/fixtures/views/test_view/lib.js +3 -0
  78. data/spec/fixtures/views/test_view/only-map.js +4 -0
  79. data/spec/fixtures/views/test_view/test-map.js +3 -0
  80. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  81. data/spec/functional/validations_spec.rb +8 -0
  82. data/spec/spec_helper.rb +60 -0
  83. data/spec/unit/active_model_lint_spec.rb +30 -0
  84. data/spec/unit/assocations_spec.rb +242 -0
  85. data/spec/unit/attachment_spec.rb +176 -0
  86. data/spec/unit/base_spec.rb +537 -0
  87. data/spec/unit/casted_spec.rb +72 -0
  88. data/spec/unit/class_proxy_spec.rb +167 -0
  89. data/spec/unit/collection_spec.rb +86 -0
  90. data/spec/unit/configuration_spec.rb +77 -0
  91. data/spec/unit/connection_spec.rb +148 -0
  92. data/spec/unit/core_extensions/time_parsing.rb +77 -0
  93. data/spec/unit/design_doc_spec.rb +241 -0
  94. data/spec/unit/designs/view_spec.rb +831 -0
  95. data/spec/unit/designs_spec.rb +134 -0
  96. data/spec/unit/dirty_spec.rb +436 -0
  97. data/spec/unit/embeddable_spec.rb +498 -0
  98. data/spec/unit/inherited_spec.rb +33 -0
  99. data/spec/unit/persistence_spec.rb +481 -0
  100. data/spec/unit/property_protection_spec.rb +192 -0
  101. data/spec/unit/property_spec.rb +481 -0
  102. data/spec/unit/proxyable_spec.rb +376 -0
  103. data/spec/unit/subclass_spec.rb +85 -0
  104. data/spec/unit/typecast_spec.rb +521 -0
  105. data/spec/unit/validations_spec.rb +140 -0
  106. data/spec/unit/view_spec.rb +367 -0
  107. metadata +301 -0
@@ -0,0 +1,176 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Model attachments" do
4
+
5
+ describe "#has_attachment?" do
6
+ before(:each) do
7
+ reset_test_db!
8
+ @obj = Basic.new
9
+ @obj.save.should be_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
+
34
+ it 'should return false if an attachment has been removed and reloaded' do
35
+ @obj.delete_attachment(@attachment_name)
36
+ reloaded_obj = Basic.get(@obj.id)
37
+ reloaded_obj.has_attachment?(@attachment_name).should be_false
38
+ end
39
+
40
+ end
41
+
42
+ describe "creating an attachment" do
43
+ before(:each) do
44
+ @obj = Basic.new
45
+ @obj.save.should be_true
46
+ @file_ext = File.open(FIXTURE_PATH + '/attachments/test.html')
47
+ @file_no_ext = File.open(FIXTURE_PATH + '/attachments/README')
48
+ @attachment_name = 'my_attachment'
49
+ @content_type = 'media/mp3'
50
+ end
51
+
52
+ it "should create an attachment from file with an extension" do
53
+ @obj.create_attachment(:file => @file_ext, :name => @attachment_name)
54
+ @obj.save.should be_true
55
+ reloaded_obj = Basic.get(@obj.id)
56
+ reloaded_obj.attachments[@attachment_name].should_not be_nil
57
+ end
58
+
59
+ it "should create an attachment from file without an extension" do
60
+ @obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
61
+ @obj.save.should be_true
62
+ reloaded_obj = Basic.get(@obj.id)
63
+ reloaded_obj.attachments[@attachment_name].should_not be_nil
64
+ end
65
+
66
+ it 'should raise ArgumentError if :file is missing' do
67
+ lambda{ @obj.create_attachment(:name => @attachment_name) }.should raise_error
68
+ end
69
+
70
+ it 'should raise ArgumentError if :name is missing' do
71
+ lambda{ @obj.create_attachment(:file => @file_ext) }.should raise_error
72
+ end
73
+
74
+ it 'should set the content-type if passed' do
75
+ @obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
76
+ @obj.attachments[@attachment_name]['content_type'].should == @content_type
77
+ end
78
+
79
+ it "should detect the content-type automatically" do
80
+ @obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
81
+ @obj.attachments['couchdb.png']['content_type'].should == "image/png"
82
+ end
83
+
84
+ it "should use name to detect the content-type automatically if no file" do
85
+ file = File.open(FIXTURE_PATH + '/attachments/couchdb.png')
86
+ file.stub!(:path).and_return("badfilname")
87
+ @obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
88
+ @obj.attachments['couchdb.png']['content_type'].should == "image/png"
89
+ end
90
+
91
+ end
92
+
93
+ describe 'reading, updating, and deleting an attachment' do
94
+ before(:each) do
95
+ @obj = Basic.new
96
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
97
+ @attachment_name = 'my_attachment'
98
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
99
+ @obj.save.should be_true
100
+ @file.rewind
101
+ @content_type = 'media/mp3'
102
+ end
103
+
104
+ it 'should read an attachment that exists' do
105
+ @obj.read_attachment(@attachment_name).should == @file.read
106
+ end
107
+
108
+ it 'should update an attachment that exists' do
109
+ file = File.open(FIXTURE_PATH + '/attachments/README')
110
+ @file.should_not == file
111
+ @obj.update_attachment(:file => file, :name => @attachment_name)
112
+ @obj.save
113
+ reloaded_obj = Basic.get(@obj.id)
114
+ file.rewind
115
+ reloaded_obj.read_attachment(@attachment_name).should_not == @file.read
116
+ reloaded_obj.read_attachment(@attachment_name).should == file.read
117
+ end
118
+
119
+ it 'should set the content-type if passed' do
120
+ file = File.open(FIXTURE_PATH + '/attachments/README')
121
+ @file.should_not == file
122
+ @obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
123
+ @obj.attachments[@attachment_name]['content_type'].should == @content_type
124
+ end
125
+
126
+ it 'should delete an attachment that exists' do
127
+ @obj.delete_attachment(@attachment_name)
128
+ @obj.save
129
+ lambda{Basic.get(@obj.id).read_attachment(@attachment_name)}.should raise_error
130
+ end
131
+ end
132
+
133
+ describe "#attachment_url" do
134
+ before(:each) do
135
+ @obj = Basic.new
136
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
137
+ @attachment_name = 'my_attachment'
138
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
139
+ @obj.save.should be_true
140
+ end
141
+
142
+ it 'should return nil if attachment does not exist' do
143
+ @obj.attachment_url('bogus').should be_nil
144
+ end
145
+
146
+ it 'should return the attachment URL as specified by CouchDB HttpDocumentApi' do
147
+ @obj.attachment_url(@attachment_name).should == "#{Basic.database}/#{@obj.id}/#{@attachment_name}"
148
+ end
149
+
150
+ it 'should return the attachment URI' do
151
+ @obj.attachment_uri(@attachment_name).should == "#{Basic.database.uri}/#{@obj.id}/#{@attachment_name}"
152
+ end
153
+ end
154
+
155
+ describe "#attachments" do
156
+ before(:each) do
157
+ @obj = Basic.new
158
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
159
+ @attachment_name = 'my_attachment'
160
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
161
+ @obj.save.should be_true
162
+ end
163
+
164
+ it 'should return an empty Hash when document does not have any attachment' do
165
+ new_obj = Basic.new
166
+ new_obj.save.should be_true
167
+ new_obj.attachments.should == {}
168
+ end
169
+
170
+ it 'should return a Hash with all attachments' do
171
+ @file.rewind
172
+ @obj.attachments.should == { @attachment_name =>{ "data" => "PCFET0NUWVBFIGh0bWw+CjxodG1sPgogIDxoZWFkPgogICAgPHRpdGxlPlRlc3Q8L3RpdGxlPgogIDwvaGVhZD4KICA8Ym9keT4KICAgIDxwPgogICAgICBUZXN0CiAgICA8L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==", "content_type" => "text/html"}}
173
+ end
174
+
175
+ end
176
+ end
@@ -0,0 +1,537 @@
1
+ # encoding: utf-8
2
+ require "spec_helper"
3
+
4
+ describe "Model Base" do
5
+
6
+ before(:each) do
7
+ @obj = WithDefaultValues.new
8
+ end
9
+
10
+ describe "instance database connection" do
11
+ it "should use the default database" do
12
+ @obj.database.name.should == 'couchrest-model-test'
13
+ end
14
+
15
+ it "should override the default db" do
16
+ @obj.database = TEST_SERVER.database!('couchrest-extendedmodel-test')
17
+ @obj.database.name.should == 'couchrest-extendedmodel-test'
18
+ @obj.database.delete!
19
+ end
20
+ end
21
+
22
+ describe "a new model" do
23
+ it "should be a new document" do
24
+ @obj = Basic.new
25
+ @obj.rev.should be_nil
26
+ @obj.should be_new
27
+ @obj.should be_new_document
28
+ @obj.should be_new_record
29
+ end
30
+
31
+ it "should not fail with nil argument" do
32
+ @obj = Basic.new(nil)
33
+ @obj.should_not be_nil
34
+ end
35
+
36
+ it "should allow the database to be set" do
37
+ @obj = Basic.new(nil, :database => 'database')
38
+ @obj.database.should eql('database')
39
+ end
40
+
41
+ it "should support initialization block" do
42
+ @obj = Basic.new {|b| b.database = 'database'}
43
+ @obj.database.should eql('database')
44
+ end
45
+
46
+ it "should only set defined properties" do
47
+ @doc = WithDefaultValues.new(:name => 'test', :foo => 'bar')
48
+ @doc['name'].should eql('test')
49
+ @doc['foo'].should be_nil
50
+ end
51
+
52
+ it "should set all properties with :directly_set_attributes option" do
53
+ @doc = WithDefaultValues.new({:name => 'test', :foo => 'bar'}, :directly_set_attributes => true)
54
+ @doc['name'].should eql('test')
55
+ @doc['foo'].should eql('bar')
56
+ end
57
+
58
+ it "should set the model type" do
59
+ @doc = WithDefaultValues.new()
60
+ @doc[WithDefaultValues.model_type_key].should eql('WithDefaultValues')
61
+ end
62
+
63
+ it "should call after_initialize method if available" do
64
+ @doc = WithAfterInitializeMethod.new
65
+ @doc['some_value'].should eql('value')
66
+ end
67
+
68
+ it "should call after_initialize after block" do
69
+ @doc = WithAfterInitializeMethod.new {|d| d.some_value = "foo"}
70
+ @doc['some_value'].should eql('foo')
71
+ end
72
+
73
+ it "should call after_initialize callback if available" do
74
+ klass = Class.new(CouchRest::Model::Base)
75
+ klass.class_eval do # for ruby 1.8.7
76
+ property :name
77
+ after_initialize :set_name
78
+ def set_name; self.name = "foobar"; end
79
+ end
80
+ @doc = klass.new
81
+ @doc.name.should eql("foobar")
82
+ end
83
+ end
84
+
85
+ describe "ActiveModel compatability Basic" do
86
+
87
+ before(:each) do
88
+ @obj = Basic.new(nil)
89
+ end
90
+
91
+ describe "#to_key" do
92
+ context "when the document is new" do
93
+ it "returns nil" do
94
+ @obj.to_key.should be_nil
95
+ end
96
+ end
97
+
98
+ context "when the document is not new" do
99
+ it "returns id in an array" do
100
+ @obj.save
101
+ @obj.to_key.should eql([@obj['_id']])
102
+ end
103
+ end
104
+ end
105
+
106
+ describe "#to_param" do
107
+ context "when the document is new" do
108
+ it "returns nil" do
109
+ @obj.to_param.should be_nil
110
+ end
111
+ end
112
+
113
+ context "when the document is not new" do
114
+ it "returns id" do
115
+ @obj.save
116
+ @obj.to_param.should eql(@obj['_id'])
117
+ end
118
+ end
119
+ end
120
+
121
+ describe "#persisted?" do
122
+ context "when the document is new" do
123
+ it "returns false" do
124
+ @obj.persisted?.should be_false
125
+ end
126
+ end
127
+
128
+ context "when the document is not new" do
129
+ it "returns id" do
130
+ @obj.save
131
+ @obj.persisted?.should be_true
132
+ end
133
+ end
134
+
135
+ context "when the document is destroyed" do
136
+ it "returns false" do
137
+ @obj.save
138
+ @obj.destroy
139
+ @obj.persisted?.should be_false
140
+ end
141
+ end
142
+ end
143
+
144
+ describe "#model_name" do
145
+ it "returns the name of the model" do
146
+ @obj.class.model_name.should eql('Basic')
147
+ WithDefaultValues.model_name.human.should eql("With default values")
148
+ end
149
+ end
150
+
151
+ describe "#destroyed?" do
152
+ it "should be present" do
153
+ @obj.should respond_to(:destroyed?)
154
+ end
155
+ it "should return false with new object" do
156
+ @obj.destroyed?.should be_false
157
+ end
158
+ it "should return true after destroy" do
159
+ @obj.save
160
+ @obj.destroy
161
+ @obj.destroyed?.should be_true
162
+ end
163
+ end
164
+ end
165
+
166
+ describe "comparisons" do
167
+ describe "#==" do
168
+ context "on saved document" do
169
+ it "should be true on same document" do
170
+ p = Project.create
171
+ p.should eql(p)
172
+ end
173
+ it "should be true after loading" do
174
+ p = Project.create
175
+ p.should eql(Project.get(p.id))
176
+ end
177
+ it "should not be true if databases do not match" do
178
+ p = Project.create
179
+ p2 = p.dup
180
+ p2.stub!(:database).and_return('other')
181
+ p.should_not eql(p2)
182
+ end
183
+ it "should always be false if one document not saved" do
184
+ p = Project.create(:name => 'test')
185
+ o = Project.new(:name => 'test')
186
+ p.should_not eql(o)
187
+ end
188
+ end
189
+ context "with new documents" do
190
+ it "should be true when attributes match" do
191
+ p = Project.new(:name => 'test')
192
+ o = Project.new(:name => 'test')
193
+ p.should eql(o)
194
+ end
195
+ it "should not be true when attributes don't match" do
196
+ p = Project.new(:name => 'test')
197
+ o = Project.new(:name => 'testing')
198
+ p.should_not eql(o)
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ describe "update attributes without saving" do
205
+ before(:each) do
206
+ a = Article.get "big-bad-danger" rescue nil
207
+ a.destroy if a
208
+ @art = Article.new(:title => "big bad danger")
209
+ @art.save
210
+ end
211
+ it "should work for attribute= methods" do
212
+ @art['title'].should == "big bad danger"
213
+ @art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
214
+ @art['title'].should == "super danger"
215
+ end
216
+ it "should silently ignore _id" do
217
+ @art.update_attributes_without_saving('_id' => 'foobar')
218
+ @art['_id'].should_not == 'foobar'
219
+ end
220
+ it "should silently ignore _rev" do
221
+ @art.update_attributes_without_saving('_rev' => 'foobar')
222
+ @art['_rev'].should_not == 'foobar'
223
+ end
224
+ it "should silently ignore created_at" do
225
+ @art.update_attributes_without_saving('created_at' => 'foobar')
226
+ @art['created_at'].should_not == 'foobar'
227
+ end
228
+ it "should silently ignore updated_at" do
229
+ @art.update_attributes_without_saving('updated_at' => 'foobar')
230
+ @art['updated_at'].should_not == 'foobar'
231
+ end
232
+ it "should also work using attributes= alias" do
233
+ @art.respond_to?(:attributes=).should be_true
234
+ @art.attributes = {'date' => Time.now, :title => "something else"}
235
+ @art['title'].should == "something else"
236
+ end
237
+
238
+ it "should not flip out if an attribute= method is missing and ignore it" do
239
+ lambda {
240
+ @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
241
+ }.should_not raise_error
242
+ @art.slug.should == "big-bad-danger"
243
+ end
244
+
245
+ #it "should not change other attributes if there is an error" do
246
+ # lambda {
247
+ # @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
248
+ # }.should raise_error
249
+ # @art['title'].should == "big bad danger"
250
+ #end
251
+ end
252
+
253
+ describe "update attributes" do
254
+ before(:each) do
255
+ a = Article.get "big-bad-danger" rescue nil
256
+ a.destroy if a
257
+ @art = Article.new(:title => "big bad danger")
258
+ @art.save
259
+ end
260
+ it "should save" do
261
+ @art['title'].should == "big bad danger"
262
+ @art.update_attributes('date' => Time.now, :title => "super danger")
263
+ loaded = Article.get(@art.id)
264
+ loaded['title'].should == "super danger"
265
+ end
266
+ end
267
+
268
+ describe "with default" do
269
+ it "should have the default value set at initalization" do
270
+ @obj.preset.should == {:right => 10, :top_align => false}
271
+ end
272
+
273
+ it "should have the default false value explicitly assigned" do
274
+ @obj.default_false.should == false
275
+ end
276
+
277
+ it "should automatically call a proc default at initialization" do
278
+ @obj.set_by_proc.should be_an_instance_of(Time)
279
+ @obj.set_by_proc.should == @obj.set_by_proc
280
+ @obj.set_by_proc.should < Time.now
281
+ end
282
+
283
+ it "should let you overwrite the default values" do
284
+ obj = WithDefaultValues.new(:preset => 'test')
285
+ obj.preset = 'test'
286
+ end
287
+
288
+ it "should keep default values for new instances" do
289
+ obj = WithDefaultValues.new
290
+ obj.preset[:alpha] = 123
291
+ obj.preset.should == {:right => 10, :top_align => false, :alpha => 123}
292
+ another = WithDefaultValues.new
293
+ another.preset.should == {:right => 10, :top_align => false}
294
+ end
295
+
296
+ it "should work with a default empty array" do
297
+ obj = WithDefaultValues.new(:tags => ['spec'])
298
+ obj.tags.should == ['spec']
299
+ end
300
+
301
+ it "should set default value of read-only property" do
302
+ obj = WithDefaultValues.new
303
+ obj.read_only_with_default.should == 'generic'
304
+ end
305
+ end
306
+
307
+ describe "simplified way of setting property types" do
308
+ it "should set defaults" do
309
+ obj = WithSimplePropertyType.new
310
+ obj.preset.should eql('none')
311
+ end
312
+
313
+ it "should handle arrays" do
314
+ obj = WithSimplePropertyType.new(:tags => ['spec'])
315
+ obj.tags.should == ['spec']
316
+ end
317
+ end
318
+
319
+ describe "a doc with template values (CR::Model spec)" do
320
+ before(:all) do
321
+ WithTemplateAndUniqueID.all.map{|o| o.destroy}
322
+ WithTemplateAndUniqueID.database.bulk_delete
323
+ @tmpl = WithTemplateAndUniqueID.new
324
+ @tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'slug' => '1')
325
+ end
326
+ it "should have fields set when new" do
327
+ @tmpl.preset.should == 'value'
328
+ end
329
+ it "shouldn't override explicitly set values" do
330
+ @tmpl2.preset.should == 'not_value'
331
+ end
332
+ it "shouldn't override existing documents" do
333
+ @tmpl2.save
334
+ tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
335
+ @tmpl2.preset.should == 'not_value'
336
+ tmpl2_reloaded.preset.should == 'not_value'
337
+ end
338
+ end
339
+
340
+
341
+ describe "finding all instances of a model" do
342
+ before(:all) do
343
+ WithTemplateAndUniqueID.all.map{|o| o.destroy}
344
+ WithTemplateAndUniqueID.database.bulk_delete
345
+ WithTemplateAndUniqueID.new('slug' => '1').save
346
+ WithTemplateAndUniqueID.new('slug' => '2').save
347
+ WithTemplateAndUniqueID.new('slug' => '3').save
348
+ WithTemplateAndUniqueID.new('slug' => '4').save
349
+ end
350
+ it "should find all" do
351
+ rs = WithTemplateAndUniqueID.all
352
+ rs.length.should == 4
353
+ end
354
+ end
355
+
356
+ describe "counting all instances of a model" do
357
+ before(:each) do
358
+ @db = reset_test_db!
359
+ end
360
+
361
+ it ".count should return 0 if there are no docuemtns" do
362
+ WithTemplateAndUniqueID.count.should == 0
363
+ end
364
+
365
+ it ".count should return the number of documents" do
366
+ WithTemplateAndUniqueID.new('slug' => '1').save
367
+ WithTemplateAndUniqueID.new('slug' => '2').save
368
+ WithTemplateAndUniqueID.new('slug' => '3').save
369
+
370
+ WithTemplateAndUniqueID.count.should == 3
371
+ end
372
+ end
373
+
374
+ describe "finding the first instance of a model" do
375
+ before(:each) do
376
+ @db = reset_test_db!
377
+ WithTemplateAndUniqueID.new('slug' => '1').save
378
+ WithTemplateAndUniqueID.new('slug' => '2').save
379
+ WithTemplateAndUniqueID.new('slug' => '3').save
380
+ WithTemplateAndUniqueID.new('slug' => '4').save
381
+ end
382
+ it "should find first" do
383
+ rs = WithTemplateAndUniqueID.first
384
+ rs['slug'].should == "1"
385
+ end
386
+ it "should return nil if no instances are found" do
387
+ WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
388
+ WithTemplateAndUniqueID.first.should be_nil
389
+ end
390
+ end
391
+
392
+
393
+ describe "getting a model with a subobject field" do
394
+ before(:all) do
395
+ course_doc = {
396
+ "title" => "Metaphysics 410",
397
+ "professor" => {
398
+ "name" => ["Mark", "Hinchliff"]
399
+ },
400
+ "ends_at" => "2008/12/19 13:00:00 +0800"
401
+ }
402
+ r = Course.database.save_doc course_doc
403
+ @course = Course.get r['id']
404
+ end
405
+ it "should load the course" do
406
+ @course["professor"]["name"][1].should == "Hinchliff"
407
+ end
408
+ it "should instantiate the professor as a person" do
409
+ @course['professor'].last_name.should == "Hinchliff"
410
+ end
411
+ it "should instantiate the ends_at as a Time" do
412
+ @course['ends_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
413
+ end
414
+ end
415
+
416
+ describe "timestamping" do
417
+ before(:each) do
418
+ oldart = Article.get "saving-this" rescue nil
419
+ oldart.destroy if oldart
420
+ @art = Article.new(:title => "Saving this")
421
+ @art.save
422
+ end
423
+
424
+ it "should define the updated_at and created_at getters and set the values" do
425
+ @obj.save
426
+ obj = WithDefaultValues.get(@obj.id)
427
+ obj.should be_an_instance_of(WithDefaultValues)
428
+ obj.created_at.should be_an_instance_of(Time)
429
+ obj.updated_at.should be_an_instance_of(Time)
430
+ obj.created_at.to_s.should == @obj.updated_at.to_s
431
+ end
432
+
433
+ it "should not change created_at on update" do
434
+ 2.times do
435
+ lambda do
436
+ @art.save
437
+ end.should_not change(@art, :created_at)
438
+ end
439
+ end
440
+
441
+ it "should set the time on create" do
442
+ (Time.now - @art.created_at).should < 2
443
+ foundart = Article.get @art.id
444
+ foundart.created_at.should == foundart.updated_at
445
+ end
446
+ it "should set the time on update" do
447
+ @art.title = "new title" # only saved if @art.changed? == true
448
+ @art.save
449
+ @art.created_at.should < @art.updated_at
450
+ end
451
+ end
452
+
453
+ describe "getter and setter methods" do
454
+ it "should try to call the arg= method before setting :arg in the hash" do
455
+ @doc = WithGetterAndSetterMethods.new(:arg => "foo")
456
+ @doc['arg'].should be_nil
457
+ @doc[:arg].should be_nil
458
+ @doc.other_arg.should == "foo-foo"
459
+ end
460
+ end
461
+
462
+ describe "recursive validation on a model" do
463
+ before :each do
464
+ reset_test_db!
465
+ @cat = Cat.new(:name => 'Sockington')
466
+ end
467
+
468
+ it "should not save if a nested casted model is invalid" do
469
+ @cat.favorite_toy = CatToy.new
470
+ @cat.should_not be_valid
471
+ @cat.save.should be_false
472
+ lambda{@cat.save!}.should raise_error
473
+ end
474
+
475
+ it "should save when nested casted model is valid" do
476
+ @cat.favorite_toy = CatToy.new(:name => 'Squeaky')
477
+ @cat.should be_valid
478
+ @cat.save.should be_true
479
+ lambda{@cat.save!}.should_not raise_error
480
+ end
481
+
482
+ it "should not save when nested collection contains an invalid casted model" do
483
+ @cat.toys = [CatToy.new(:name => 'Feather'), CatToy.new]
484
+ @cat.should_not be_valid
485
+ @cat.save.should be_false
486
+ lambda{@cat.save!}.should raise_error
487
+ end
488
+
489
+ it "should save when nested collection contains valid casted models" do
490
+ @cat.toys = [CatToy.new(:name => 'feather'), CatToy.new(:name => 'ball-o-twine')]
491
+ @cat.should be_valid
492
+ @cat.save.should be_true
493
+ lambda{@cat.save!}.should_not raise_error
494
+ end
495
+
496
+ it "should not fail if the nested casted model doesn't have validation" do
497
+ Cat.property :trainer, Person
498
+ Cat.validates_presence_of :name
499
+ cat = Cat.new(:name => 'Mr Bigglesworth')
500
+ cat.trainer = Person.new
501
+ cat.should be_valid
502
+ cat.save.should be_true
503
+ end
504
+ end
505
+
506
+ describe "searching the contents of a model" do
507
+ before :each do
508
+ @db = reset_test_db!
509
+
510
+ names = ["Fuzzy", "Whiskers", "Mr Bigglesworth", "Sockington", "Smitty", "Sammy", "Samson", "Simon"]
511
+ names.each { |name| Cat.create(:name => name) }
512
+
513
+ search_function = { 'defaults' => {'store' => 'no', 'index' => 'analyzed_no_norms'},
514
+ 'index' => "function(doc) { ret = new Document(); ret.add(doc['name'], {'field':'name'}); return ret; }" }
515
+ @db.save_doc({'_id' => '_design/search', 'fulltext' => {'cats' => search_function}})
516
+ end
517
+
518
+ it "should be able to paginate through a large set of search results" do
519
+ if couchdb_lucene_available?
520
+ names = []
521
+ Cat.paginated_each(:design_doc => "search", :view_name => "cats",
522
+ :q => 'name:S*', :search => true, :include_docs => true, :per_page => 3) do |cat|
523
+ cat.should_not be_nil
524
+ names << cat.name
525
+ end
526
+
527
+ names.size.should == 5
528
+ names.should include('Sockington')
529
+ names.should include('Smitty')
530
+ names.should include('Sammy')
531
+ names.should include('Samson')
532
+ names.should include('Simon')
533
+ end
534
+ end
535
+ end
536
+
537
+ end