openlogic-couchrest_model 1.0.0

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