derfred-couchrest 0.12.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +68 -0
  3. data/Rakefile +66 -0
  4. data/THANKS.md +18 -0
  5. data/examples/model/example.rb +138 -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/commands/generate.rb +71 -0
  17. data/lib/couchrest/commands/push.rb +103 -0
  18. data/lib/couchrest/core/database.rb +314 -0
  19. data/lib/couchrest/core/design.rb +89 -0
  20. data/lib/couchrest/core/document.rb +101 -0
  21. data/lib/couchrest/core/model.rb +615 -0
  22. data/lib/couchrest/core/server.rb +88 -0
  23. data/lib/couchrest/core/view.rb +4 -0
  24. data/lib/couchrest/helper/pager.rb +103 -0
  25. data/lib/couchrest/helper/streamer.rb +44 -0
  26. data/lib/couchrest/monkeypatches.rb +99 -0
  27. data/lib/couchrest.rb +161 -0
  28. data/spec/couchrest/core/couchrest_spec.rb +201 -0
  29. data/spec/couchrest/core/database_spec.rb +740 -0
  30. data/spec/couchrest/core/design_spec.rb +131 -0
  31. data/spec/couchrest/core/document_spec.rb +311 -0
  32. data/spec/couchrest/core/model_spec.rb +855 -0
  33. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  34. data/spec/couchrest/helpers/streamer_spec.rb +23 -0
  35. data/spec/fixtures/attachments/README +3 -0
  36. data/spec/fixtures/attachments/couchdb.png +0 -0
  37. data/spec/fixtures/attachments/test.html +11 -0
  38. data/spec/fixtures/views/lib.js +3 -0
  39. data/spec/fixtures/views/test_view/lib.js +3 -0
  40. data/spec/fixtures/views/test_view/only-map.js +4 -0
  41. data/spec/fixtures/views/test_view/test-map.js +3 -0
  42. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  43. data/spec/spec.opts +6 -0
  44. data/spec/spec_helper.rb +21 -0
  45. data/utils/remap.rb +27 -0
  46. data/utils/subset.rb +30 -0
  47. metadata +143 -0
@@ -0,0 +1,131 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe CouchRest::Design do
4
+
5
+ describe "defining a view" do
6
+ it "should add a view to the design doc" do
7
+ @des = CouchRest::Design.new
8
+ method = @des.view_by :name
9
+ method.should == "by_name"
10
+ @des["views"]["by_name"].should_not be_nil
11
+ end
12
+ end
13
+
14
+ describe "with an unsaved view" do
15
+ before(:each) do
16
+ @des = CouchRest::Design.new
17
+ method = @des.view_by :name
18
+ end
19
+ it "should accept a name" do
20
+ @des.name = "mytest"
21
+ @des.name.should == "mytest"
22
+ end
23
+ it "should not save on view definition" do
24
+ @des.rev.should be_nil
25
+ end
26
+ it "should freak out on view access" do
27
+ lambda{@des.view :by_name}.should raise_error
28
+ end
29
+ end
30
+
31
+ describe "saving" do
32
+ before(:each) do
33
+ @des = CouchRest::Design.new
34
+ method = @des.view_by :name
35
+ @des.database = reset_test_db!
36
+ end
37
+ it "should fail without a name" do
38
+ lambda{@des.save}.should raise_error(ArgumentError)
39
+ end
40
+ it "should work with a name" do
41
+ @des.name = "myview"
42
+ @des.save
43
+ end
44
+ end
45
+
46
+ describe "when it's saved" do
47
+ before(:each) do
48
+ @db = reset_test_db!
49
+ @db.bulk_save([{"name" => "x"},{"name" => "y"}])
50
+ @des = CouchRest::Design.new
51
+ @des.database = @db
52
+ method = @des.view_by :name
53
+ end
54
+ it "should by queryable when it's saved" do
55
+ @des.name = "mydesign"
56
+ @des.save
57
+ res = @des.view :by_name
58
+ res["rows"][0]["key"].should == "x"
59
+ end
60
+ end
61
+
62
+ describe "from a saved document" do
63
+ before(:each) do
64
+ @db = reset_test_db!
65
+ @db.save_doc({
66
+ "_id" => "_design/test",
67
+ "views" => {
68
+ "by_name" => {
69
+ "map" => "function(doc){if (doc.name) emit(doc.name, null)}"
70
+ }
71
+ }
72
+ })
73
+ @db.bulk_save([{"name" => "a"},{"name" => "b"}])
74
+ @des = @db.get "_design/test"
75
+ end
76
+ it "should be a Design" do
77
+ @des.should be_an_instance_of(CouchRest::Design)
78
+ end
79
+ it "should have a modifiable name" do
80
+ @des.name.should == "test"
81
+ @des.name = "supertest"
82
+ @des.id.should == "_design/supertest"
83
+ end
84
+ it "should by queryable" do
85
+ res = @des.view :by_name
86
+ res["rows"][0]["key"].should == "a"
87
+ end
88
+ end
89
+
90
+ describe "a view with default options" do
91
+ before(:all) do
92
+ @db = reset_test_db!
93
+ @des = CouchRest::Design.new
94
+ @des.name = "test"
95
+ method = @des.view_by :name, :descending => true
96
+ @des.database = @db
97
+ @des.save
98
+ @db.bulk_save([{"name" => "a"},{"name" => "z"}])
99
+ end
100
+ it "should save them" do
101
+ @d2 = @db.get(@des.id)
102
+ @d2["views"]["by_name"]["couchrest-defaults"].should == {"descending"=>true}
103
+ end
104
+ it "should use them" do
105
+ res = @des.view :by_name
106
+ res["rows"].first["key"].should == "z"
107
+ end
108
+ it "should override them" do
109
+ res = @des.view :by_name, :descending => false
110
+ res["rows"].first["key"].should == "a"
111
+ end
112
+ end
113
+
114
+ describe "a view with multiple keys" do
115
+ before(:all) do
116
+ @db = reset_test_db!
117
+ @des = CouchRest::Design.new
118
+ @des.name = "test"
119
+ method = @des.view_by :name, :age
120
+ @des.database = @db
121
+ @des.save
122
+ @db.bulk_save([{"name" => "a", "age" => 2},
123
+ {"name" => "a", "age" => 4},{"name" => "z", "age" => 9}])
124
+ end
125
+ it "should work" do
126
+ res = @des.view :by_name_and_age
127
+ res["rows"].first["key"].should == ["a",2]
128
+ end
129
+ end
130
+
131
+ end
@@ -0,0 +1,311 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ class Video < CouchRest::Document; end
4
+
5
+ describe CouchRest::Document do
6
+
7
+ before(:all) do
8
+ @couch = CouchRest.new
9
+ @db = @couch.database!(TESTDB)
10
+ end
11
+
12
+ describe "[]=" do
13
+ before(:each) do
14
+ @doc = CouchRest::Document.new
15
+ end
16
+ it "should work" do
17
+ @doc["enamel"].should == nil
18
+ @doc["enamel"] = "Strong"
19
+ @doc["enamel"].should == "Strong"
20
+ end
21
+ it "[]= should convert to string" do
22
+ @doc["enamel"].should == nil
23
+ @doc[:enamel] = "Strong"
24
+ @doc["enamel"].should == "Strong"
25
+ end
26
+ it "should read as a string" do
27
+ @doc[:enamel] = "Strong"
28
+ @doc[:enamel].should == "Strong"
29
+ end
30
+ end
31
+
32
+ describe "default database" do
33
+ before(:each) do
34
+ Video.use_database nil
35
+ end
36
+ it "should be set using use_database on the model" do
37
+ Video.new.database.should be_nil
38
+ Video.use_database @db
39
+ Video.new.database.should == @db
40
+ Video.use_database nil
41
+ end
42
+
43
+ it "should be overwritten by instance" do
44
+ db = @couch.database('test')
45
+ article = Video.new
46
+ article.database.should be_nil
47
+ article.database = db
48
+ article.database.should_not be_nil
49
+ article.database.should == db
50
+ end
51
+ end
52
+
53
+ describe "new" do
54
+ before(:each) do
55
+ @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
56
+ end
57
+ it "should create itself from a Hash" do
58
+ @doc["key"].should == [1,2,3]
59
+ @doc["more"].should == "values"
60
+ end
61
+ it "should not have rev and id" do
62
+ @doc.rev.should be_nil
63
+ @doc.id.should be_nil
64
+ end
65
+
66
+ it "should freak out when saving without a database" do
67
+ lambda{@doc.save}.should raise_error(ArgumentError)
68
+ end
69
+
70
+ end
71
+
72
+ # move to database spec
73
+ describe "saving using a database" do
74
+ before(:all) do
75
+ @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
76
+ @db = reset_test_db!
77
+ @resp = @db.save_doc(@doc)
78
+ end
79
+ it "should apply the database" do
80
+ @doc.database.should == @db
81
+ end
82
+ it "should get id and rev" do
83
+ @doc.id.should == @resp["id"]
84
+ @doc.rev.should == @resp["rev"]
85
+ end
86
+ end
87
+
88
+ describe "bulk saving" do
89
+ before :all do
90
+ @db = reset_test_db!
91
+ end
92
+
93
+ it "should use the document bulk save cache" do
94
+ doc = CouchRest::Document.new({"_id" => "bulkdoc", "val" => 3})
95
+ doc.database = @db
96
+ doc.save(true)
97
+ lambda { doc.database.get(doc["_id"]) }.should raise_error(RestClient::ResourceNotFound)
98
+ doc.database.bulk_save
99
+ doc.database.get(doc["_id"])["val"].should == doc["val"]
100
+ end
101
+ end
102
+
103
+ describe "getting from a database" do
104
+ before(:all) do
105
+ @db = reset_test_db!
106
+ @resp = @db.save_doc({
107
+ "key" => "value"
108
+ })
109
+ @doc = @db.get @resp['id']
110
+ end
111
+ it "should return a document" do
112
+ @doc.should be_an_instance_of(CouchRest::Document)
113
+ end
114
+ it "should have a database" do
115
+ @doc.database.should == @db
116
+ end
117
+ it "should be saveable and resavable" do
118
+ @doc["more"] = "keys"
119
+ @doc.save
120
+ @db.get(@resp['id'])["more"].should == "keys"
121
+ @doc["more"] = "these keys"
122
+ @doc.save
123
+ @db.get(@resp['id'])["more"].should == "these keys"
124
+ end
125
+ end
126
+
127
+ describe "destroying a document from a db" do
128
+ before(:all) do
129
+ @db = reset_test_db!
130
+ @resp = @db.save_doc({
131
+ "key" => "value"
132
+ })
133
+ @doc = @db.get @resp['id']
134
+ end
135
+ it "should make it disappear" do
136
+ @doc.destroy
137
+ lambda{@db.get @resp['id']}.should raise_error
138
+ end
139
+ it "should error when there's no db" do
140
+ @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
141
+ lambda{@doc.destroy}.should raise_error(ArgumentError)
142
+ end
143
+ end
144
+
145
+
146
+ describe "destroying a document from a db using bulk save" do
147
+ before(:all) do
148
+ @db = reset_test_db!
149
+ @resp = @db.save_doc({
150
+ "key" => "value"
151
+ })
152
+ @doc = @db.get @resp['id']
153
+ end
154
+ it "should defer actual deletion" do
155
+ @doc.destroy(true)
156
+ @doc['_id'].should == nil
157
+ @doc['_rev'].should == nil
158
+ lambda{@db.get @resp['id']}.should_not raise_error
159
+ @db.bulk_save
160
+ lambda{@db.get @resp['id']}.should raise_error
161
+ end
162
+ end
163
+
164
+ describe "copying a document" do
165
+ before :each do
166
+ @db = reset_test_db!
167
+ @resp = @db.save_doc({'key' => 'value'})
168
+ @docid = 'new-location'
169
+ @doc = @db.get(@resp['id'])
170
+ end
171
+ describe "to a new location" do
172
+ it "should work" do
173
+ @doc.copy @docid
174
+ newdoc = @db.get(@docid)
175
+ newdoc['key'].should == 'value'
176
+ end
177
+ it "should fail without a database" do
178
+ lambda{CouchRest::Document.new({"not"=>"a real doc"}).copy}.should raise_error(ArgumentError)
179
+ end
180
+ end
181
+ describe "to an existing location" do
182
+ before :each do
183
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
184
+ end
185
+ it "should fail without a rev" do
186
+ lambda{@doc.copy @docid}.should raise_error(RestClient::RequestFailed)
187
+ end
188
+ it "should succeed with a rev" do
189
+ @to_be_overwritten = @db.get(@docid)
190
+ @doc.copy "#{@docid}?rev=#{@to_be_overwritten['_rev']}"
191
+ newdoc = @db.get(@docid)
192
+ newdoc['key'].should == 'value'
193
+ end
194
+ it "should succeed given the doc to overwrite" do
195
+ @to_be_overwritten = @db.get(@docid)
196
+ @doc.copy @to_be_overwritten
197
+ newdoc = @db.get(@docid)
198
+ newdoc['key'].should == 'value'
199
+ end
200
+ end
201
+ end
202
+
203
+ describe "MOVE existing document" do
204
+ before :each do
205
+ @db = reset_test_db!
206
+ @resp = @db.save_doc({'key' => 'value'})
207
+ @docid = 'new-location'
208
+ @doc = @db.get(@resp['id'])
209
+ end
210
+ describe "to a new location" do
211
+ it "should work" do
212
+ @doc.move @docid
213
+ newdoc = @db.get(@docid)
214
+ newdoc['key'].should == 'value'
215
+ lambda {@db.get(@resp['id'])}.should raise_error(RestClient::ResourceNotFound)
216
+ end
217
+ it "should fail without a database" do
218
+ lambda{CouchRest::Document.new({"not"=>"a real doc"}).move}.should raise_error(ArgumentError)
219
+ lambda{CouchRest::Document.new({"_id"=>"not a real doc"}).move}.should raise_error(ArgumentError)
220
+ end
221
+ end
222
+ describe "to an existing location" do
223
+ before :each do
224
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
225
+ end
226
+ it "should fail without a rev" do
227
+ lambda{@doc.move @docid}.should raise_error(RestClient::RequestFailed)
228
+ lambda{@db.get(@resp['id'])}.should_not raise_error
229
+ end
230
+ it "should succeed with a rev" do
231
+ @to_be_overwritten = @db.get(@docid)
232
+ @doc.move "#{@docid}?rev=#{@to_be_overwritten['_rev']}"
233
+ newdoc = @db.get(@docid)
234
+ newdoc['key'].should == 'value'
235
+ lambda {@db.get(@resp['id'])}.should raise_error(RestClient::ResourceNotFound)
236
+ end
237
+ it "should succeed given the doc to overwrite" do
238
+ @to_be_overwritten = @db.get(@docid)
239
+ @doc.move @to_be_overwritten
240
+ newdoc = @db.get(@docid)
241
+ newdoc['key'].should == 'value'
242
+ lambda {@db.get(@resp['id'])}.should raise_error(RestClient::ResourceNotFound)
243
+ end
244
+ end
245
+ end
246
+ end
247
+
248
+ describe "dealing with attachments" do
249
+ before do
250
+ @db = reset_test_db!
251
+ @attach = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
252
+ response = @db.save({'key' => 'value'})
253
+ @doc = @db.get(response['id'])
254
+ end
255
+
256
+ def append_attachment(name='test.html', attach=@attach)
257
+ @doc['_attachments'] ||= {}
258
+ @doc['_attachments'][name] = {
259
+ 'type' => 'text/html',
260
+ 'data' => attach
261
+ }
262
+ @doc.save
263
+ @rev = @doc['_rev']
264
+ end
265
+
266
+ describe "PUTing an attachment directly to the doc" do
267
+ before do
268
+ @doc.put_attachment('test.html', @attach)
269
+ end
270
+
271
+ it "is there" do
272
+ @db.fetch_attachment(@doc, 'test.html').should == @attach
273
+ end
274
+
275
+ it "updates the revision" do
276
+ @doc['_rev'].should_not == @rev
277
+ end
278
+
279
+ it "updates attachments" do
280
+ @attach2 = "<html><head><title>My Doc</title></head><body><p>Is Different.</p></body></html>"
281
+ @doc.put_attachment('test.html', @attach2)
282
+ @db.fetch_attachment(@doc, 'test.html').should == @attach2
283
+ end
284
+ end
285
+
286
+ describe "fetching an attachment from a doc directly" do
287
+ before do
288
+ append_attachment
289
+ end
290
+
291
+ it "pulls the attachment" do
292
+ @doc.fetch_attachment('test.html').should == @attach
293
+ end
294
+ end
295
+
296
+ describe "deleting an attachment from a doc directly" do
297
+ before do
298
+ append_attachment
299
+ @doc.delete_attachment('test.html')
300
+ end
301
+
302
+ it "removes it" do
303
+ lambda { @db.fetch_attachment(@doc, 'test.html').should }.should raise_error(RestClient::ResourceNotFound)
304
+ end
305
+
306
+ it "updates the revision" do
307
+ @doc['_rev'].should_not == @rev
308
+ end
309
+ end
310
+
311
+ end
@@ -0,0 +1,855 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ class Basic < CouchRest::Model
4
+ end
5
+
6
+ class BasicWithValidation < CouchRest::Model
7
+
8
+ before :save, :validate
9
+ key_accessor :name
10
+
11
+ def validate
12
+ throw(:halt, false) unless name
13
+ end
14
+ end
15
+
16
+ class WithTemplateAndUniqueID < CouchRest::Model
17
+ unique_id do |model|
18
+ model['important-field']
19
+ end
20
+ set_default({
21
+ :preset => 'value',
22
+ 'more-template' => [1,2,3]
23
+ })
24
+ key_accessor :preset
25
+ key_accessor :has_no_default
26
+ end
27
+
28
+ class Question < CouchRest::Model
29
+ key_accessor :q, :a
30
+ couchrest_type = 'Question'
31
+ end
32
+
33
+ class Person < CouchRest::Model
34
+ key_accessor :name
35
+ def last_name
36
+ name.last
37
+ end
38
+ end
39
+
40
+ class Course < CouchRest::Model
41
+ key_accessor :title
42
+ cast :questions, :as => ['Question']
43
+ cast :professor, :as => 'Person'
44
+ cast :final_test_at, :as => 'Time'
45
+ view_by :title
46
+ view_by :dept, :ducktype => true
47
+ end
48
+
49
+ class Article < CouchRest::Model
50
+ use_database CouchRest.database!('http://127.0.0.1:5984/couchrest-model-test')
51
+ unique_id :slug
52
+
53
+ view_by :date, :descending => true
54
+ view_by :user_id, :date
55
+
56
+ view_by :tags,
57
+ :map =>
58
+ "function(doc) {
59
+ if (doc['couchrest-type'] == 'Article' && doc.tags) {
60
+ doc.tags.forEach(function(tag){
61
+ emit(tag, 1);
62
+ });
63
+ }
64
+ }",
65
+ :reduce =>
66
+ "function(keys, values, rereduce) {
67
+ return sum(values);
68
+ }"
69
+
70
+ key_writer :date
71
+ key_reader :slug, :created_at, :updated_at
72
+ key_accessor :title, :tags
73
+
74
+ timestamps!
75
+
76
+ before(:save, :generate_slug_from_title)
77
+ def generate_slug_from_title
78
+ self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new_document?
79
+ end
80
+ end
81
+
82
+ class Player < CouchRest::Model
83
+ unique_id :email
84
+
85
+ key_accessor :email, :name, :str, :coord, :int, :con, :spirit, :level, :xp, :points, :coins, :date, :items, :loc
86
+
87
+ view_by :name, :descending => true
88
+ view_by :loc
89
+
90
+ timestamps!
91
+ end
92
+
93
+ class Event < CouchRest::Model
94
+ key_accessor :subject, :occurs_at
95
+
96
+ cast :occurs_at, :as => 'Time', :send => 'parse'
97
+ end
98
+
99
+ describe "save bug" do
100
+ before(:each) do
101
+ CouchRest::Model.default_database = reset_test_db!
102
+ end
103
+
104
+ it "should fix" do
105
+ @p = Player.new
106
+ @p.email = 'insane@fakestreet.com'
107
+ @p.save
108
+ end
109
+ end
110
+
111
+
112
+ describe CouchRest::Model do
113
+ before(:all) do
114
+ @cr = CouchRest.new(COUCHHOST)
115
+ @db = @cr.database(TESTDB)
116
+ @db.delete! rescue nil
117
+ @db = @cr.create_db(TESTDB) rescue nil
118
+ @adb = @cr.database('couchrest-model-test')
119
+ @adb.delete! rescue nil
120
+ CouchRest.database!('http://127.0.0.1:5984/couchrest-model-test')
121
+ CouchRest::Model.default_database = CouchRest.database!('http://127.0.0.1:5984/couchrest-test')
122
+ end
123
+
124
+ it "should use the default database" do
125
+ Basic.database.info['db_name'].should == 'couchrest-test'
126
+ end
127
+
128
+ it "should override the default db" do
129
+ Article.database.info['db_name'].should == 'couchrest-model-test'
130
+ end
131
+
132
+ describe "a new model" do
133
+ it "should be a new_record" do
134
+ @obj = Basic.new
135
+ @obj.rev.should be_nil
136
+ @obj.should be_a_new_record
137
+ end
138
+ end
139
+
140
+ describe "a model with key_accessors" do
141
+ it "should allow reading keys" do
142
+ @art = Article.new
143
+ @art['title'] = 'My Article Title'
144
+ @art.title.should == 'My Article Title'
145
+ end
146
+ it "should allow setting keys" do
147
+ @art = Article.new
148
+ @art.title = 'My Article Title'
149
+ @art['title'].should == 'My Article Title'
150
+ end
151
+ end
152
+
153
+ describe "a model with key_writers" do
154
+ it "should allow setting keys" do
155
+ @art = Article.new
156
+ t = Time.now
157
+ @art.date = t
158
+ @art['date'].should == t
159
+ end
160
+ it "should not allow reading keys" do
161
+ @art = Article.new
162
+ t = Time.now
163
+ @art.date = t
164
+ lambda{@art.date}.should raise_error
165
+ end
166
+ end
167
+
168
+ describe "a model with key_readers" do
169
+ it "should allow reading keys" do
170
+ @art = Article.new
171
+ @art['slug'] = 'my-slug'
172
+ @art.slug.should == 'my-slug'
173
+ end
174
+ it "should not allow setting keys" do
175
+ @art = Article.new
176
+ lambda{@art.slug = 'My Article Title'}.should raise_error
177
+ end
178
+ end
179
+
180
+ describe "update attributes without saving" do
181
+ before(:each) do
182
+ a = Article.get "big-bad-danger" rescue nil
183
+ a.destroy if a
184
+ @art = Article.new(:title => "big bad danger")
185
+ @art.save
186
+ end
187
+ it "should work for attribute= methods" do
188
+ @art['title'].should == "big bad danger"
189
+ @art.update_attributes('date' => Time.now, :title => "super danger")
190
+ @art['title'].should == "super danger"
191
+ end
192
+
193
+ it "should flip out if an attribute= method is missing" do
194
+ lambda {
195
+ @art.update_attributes('slug' => "new-slug", :title => "super danger")
196
+ }.should raise_error
197
+ end
198
+
199
+ it "should not change other attributes if there is an error" do
200
+ lambda {
201
+ @art.update_attributes('slug' => "new-slug", :title => "super danger")
202
+ }.should raise_error
203
+ @art['title'].should == "big bad danger"
204
+ end
205
+
206
+ end
207
+
208
+ describe "update attributes" do
209
+ before(:each) do
210
+ a = Article.get "big-bad-danger" rescue nil
211
+ a.destroy if a
212
+ @art = Article.new(:title => "big bad danger")
213
+ @art.save
214
+ end
215
+ it "should save" do
216
+ @art['title'].should == "big bad danger"
217
+ @art.update_attributes('date' => Time.now, :title => "super danger")
218
+ loaded = Article.get @art.id
219
+ loaded['title'].should == "super danger"
220
+ end
221
+ end
222
+
223
+ describe "a model with template values" do
224
+ before(:all) do
225
+ @tmpl = WithTemplateAndUniqueID.new
226
+ @tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1')
227
+ end
228
+ it "should have fields set when new" do
229
+ @tmpl.preset.should == 'value'
230
+ end
231
+ it "shouldn't override explicitly set values" do
232
+ @tmpl2.preset.should == 'not_value'
233
+ end
234
+ it "shouldn't override existing documents" do
235
+ @tmpl2.save
236
+ tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
237
+ @tmpl2.preset.should == 'not_value'
238
+ tmpl2_reloaded.preset.should == 'not_value'
239
+ end
240
+ it "shouldn't fill in existing documents" do
241
+ @tmpl2.save
242
+ # If user adds a new default value, shouldn't be retroactively applied to
243
+ # documents upon fetching
244
+ WithTemplateAndUniqueID.set_default({:has_no_default => 'giraffe'})
245
+
246
+ tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
247
+ @tmpl2.has_no_default.should be_nil
248
+ tmpl2_reloaded.has_no_default.should be_nil
249
+ WithTemplateAndUniqueID.new.has_no_default.should == 'giraffe'
250
+ end
251
+ end
252
+
253
+ describe "getting a model" do
254
+ before(:all) do
255
+ @art = Article.new(:title => 'All About Getting')
256
+ @art.save
257
+ end
258
+ it "should load and instantiate it" do
259
+ foundart = Article.get @art.id
260
+ foundart.title.should == "All About Getting"
261
+ end
262
+ end
263
+
264
+ describe "getting a model with a subobjects array" do
265
+ before(:all) do
266
+ course_doc = {
267
+ "title" => "Metaphysics 200",
268
+ "questions" => [
269
+ {
270
+ "q" => "Carve the ___ of reality at the ___.",
271
+ "a" => ["beast","joints"]
272
+ },{
273
+ "q" => "Who layed the smack down on Leibniz's Law?",
274
+ "a" => "Willard Van Orman Quine"
275
+ }
276
+ ]
277
+ }
278
+ r = Course.database.save_doc course_doc
279
+ @course = Course.get r['id']
280
+ end
281
+ it "should load the course" do
282
+ @course.title.should == "Metaphysics 200"
283
+ end
284
+ it "should instantiate them as such" do
285
+ @course["questions"][0].a[0].should == "beast"
286
+ end
287
+ end
288
+
289
+ describe "finding all instances of a model" do
290
+ before(:all) do
291
+ WithTemplateAndUniqueID.new('important-field' => '1').save
292
+ WithTemplateAndUniqueID.new('important-field' => '2').save
293
+ WithTemplateAndUniqueID.new('important-field' => '3').save
294
+ WithTemplateAndUniqueID.new('important-field' => '4').save
295
+ end
296
+ it "should make the design doc" do
297
+ WithTemplateAndUniqueID.all
298
+ d = WithTemplateAndUniqueID.design_doc
299
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
300
+ end
301
+ it "should find all" do
302
+ rs = WithTemplateAndUniqueID.all
303
+ rs.length.should == 4
304
+ end
305
+ end
306
+
307
+ describe "finding the first instance of a model" do
308
+ before(:each) do
309
+ @db = reset_test_db!
310
+ WithTemplateAndUniqueID.new('important-field' => '1').save
311
+ WithTemplateAndUniqueID.new('important-field' => '2').save
312
+ WithTemplateAndUniqueID.new('important-field' => '3').save
313
+ WithTemplateAndUniqueID.new('important-field' => '4').save
314
+ end
315
+ it "should make the design doc" do
316
+ WithTemplateAndUniqueID.all
317
+ d = WithTemplateAndUniqueID.design_doc
318
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
319
+ end
320
+ it "should find first" do
321
+ rs = WithTemplateAndUniqueID.first
322
+ rs['important-field'].should == "1"
323
+ end
324
+ it "should return nil if no instances are found" do
325
+ WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
326
+ WithTemplateAndUniqueID.first.should be_nil
327
+ end
328
+ end
329
+
330
+ describe "getting a model with a subobject field" do
331
+ before(:all) do
332
+ course_doc = {
333
+ "title" => "Metaphysics 410",
334
+ "professor" => {
335
+ "name" => ["Mark", "Hinchliff"]
336
+ },
337
+ "final_test_at" => "2008/12/19 13:00:00 +0800"
338
+ }
339
+ r = Course.database.save_doc course_doc
340
+ @course = Course.get r['id']
341
+ end
342
+ it "should load the course" do
343
+ @course["professor"]["name"][1].should == "Hinchliff"
344
+ end
345
+ it "should instantiate the professor as a person" do
346
+ @course['professor'].last_name.should == "Hinchliff"
347
+ end
348
+ it "should instantiate the final_test_at as a Time" do
349
+ @course['final_test_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
350
+ end
351
+ end
352
+
353
+ describe "cast keys to any type" do
354
+ before(:all) do
355
+ event_doc = { :subject => "Some event", :occurs_at => Time.now }
356
+ e = Event.database.save_doc event_doc
357
+
358
+ @event = Event.get e['id']
359
+ end
360
+ it "should cast created_at to Time" do
361
+ @event['occurs_at'].should be_an_instance_of(Time)
362
+ end
363
+ end
364
+
365
+ describe "saving a model" do
366
+ before(:all) do
367
+ @obj = Basic.new
368
+ @obj.save.should == true
369
+ end
370
+
371
+ it "should save the doc" do
372
+ doc = @obj.database.get @obj.id
373
+ doc['_id'].should == @obj.id
374
+ end
375
+
376
+ it "should be set for resaving" do
377
+ rev = @obj.rev
378
+ @obj['another-key'] = "some value"
379
+ @obj.save
380
+ @obj.rev.should_not == rev
381
+ end
382
+
383
+ it "should set the id" do
384
+ @obj.id.should be_an_instance_of(String)
385
+ end
386
+
387
+ it "should set the type" do
388
+ @obj['couchrest-type'].should == 'Basic'
389
+ end
390
+ end
391
+
392
+ describe "saving a model with validation hooks added as extlib" do
393
+ before(:all) do
394
+ @obj = BasicWithValidation.new
395
+ end
396
+
397
+ it "save should return false is the model doesn't save as expected" do
398
+ @obj.save.should be_false
399
+ end
400
+
401
+ it "save! should raise and exception if the model doesn't save" do
402
+ lambda{ @obj.save!}.should raise_error("#{@obj.inspect} failed to save")
403
+ end
404
+
405
+ end
406
+
407
+ describe "saving a model with a unique_id configured" do
408
+ before(:each) do
409
+ @art = Article.new
410
+ @old = Article.database.get('this-is-the-title') rescue nil
411
+ Article.database.delete_doc(@old) if @old
412
+ end
413
+
414
+ it "should be a new document" do
415
+ @art.should be_a_new_document
416
+ @art.title.should be_nil
417
+ end
418
+
419
+ it "should require the title" do
420
+ lambda{@art.save}.should raise_error
421
+ @art.title = 'This is the title'
422
+ @art.save.should == true
423
+ end
424
+
425
+ it "should not change the slug on update" do
426
+ @art.title = 'This is the title'
427
+ @art.save.should == true
428
+ @art.title = 'new title'
429
+ @art.save.should == true
430
+ @art.slug.should == 'this-is-the-title'
431
+ end
432
+
433
+ it "should raise an error when the slug is taken" do
434
+ @art.title = 'This is the title'
435
+ @art.save.should == true
436
+ @art2 = Article.new(:title => 'This is the title!')
437
+ lambda{@art2.save}.should raise_error
438
+ end
439
+
440
+ it "should set the slug" do
441
+ @art.title = 'This is the title'
442
+ @art.save.should == true
443
+ @art.slug.should == 'this-is-the-title'
444
+ end
445
+
446
+ it "should set the id" do
447
+ @art.title = 'This is the title'
448
+ @art.save.should == true
449
+ @art.id.should == 'this-is-the-title'
450
+ end
451
+ end
452
+
453
+ describe "saving a model with a unique_id lambda" do
454
+ before(:each) do
455
+ @templated = WithTemplateAndUniqueID.new
456
+ @old = WithTemplateAndUniqueID.get('very-important') rescue nil
457
+ @old.destroy if @old
458
+ end
459
+
460
+ it "should require the field" do
461
+ lambda{@templated.save}.should raise_error
462
+ @templated['important-field'] = 'very-important'
463
+ @templated.save.should == true
464
+ end
465
+
466
+ it "should save with the id" do
467
+ @templated['important-field'] = 'very-important'
468
+ @templated.save.should == true
469
+ t = WithTemplateAndUniqueID.get('very-important')
470
+ t.should == @templated
471
+ end
472
+
473
+ it "should not change the id on update" do
474
+ @templated['important-field'] = 'very-important'
475
+ @templated.save.should == true
476
+ @templated['important-field'] = 'not-important'
477
+ @templated.save.should == true
478
+ t = WithTemplateAndUniqueID.get('very-important')
479
+ t.should == @templated
480
+ end
481
+
482
+ it "should raise an error when the id is taken" do
483
+ @templated['important-field'] = 'very-important'
484
+ @templated.save.should == true
485
+ lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
486
+ end
487
+
488
+ it "should set the id" do
489
+ @templated['important-field'] = 'very-important'
490
+ @templated.save.should == true
491
+ @templated.id.should == 'very-important'
492
+ end
493
+ end
494
+
495
+ describe "a model with timestamps" do
496
+ before(:each) do
497
+ oldart = Article.get "saving-this" rescue nil
498
+ oldart.destroy if oldart
499
+ @art = Article.new(:title => "Saving this")
500
+ @art.save
501
+ end
502
+ it "should set the time on create" do
503
+ (Time.now - @art.created_at).should < 2
504
+ foundart = Article.get @art.id
505
+ foundart.created_at.should == foundart.updated_at
506
+ end
507
+ it "should set the time on update" do
508
+ @art.save
509
+ @art.created_at.should < @art.updated_at
510
+ end
511
+ end
512
+
513
+ describe "a model with simple views and a default param" do
514
+ before(:all) do
515
+ written_at = Time.now - 24 * 3600 * 7
516
+ @titles = ["this and that", "also interesting", "more fun", "some junk"]
517
+ @titles.each do |title|
518
+ a = Article.new(:title => title)
519
+ a.date = written_at
520
+ a.save
521
+ written_at += 24 * 3600
522
+ end
523
+ end
524
+
525
+ it "should have a design doc" do
526
+ Article.design_doc["views"]["by_date"].should_not be_nil
527
+ end
528
+
529
+ it "should save the design doc" do
530
+ Article.by_date #rescue nil
531
+ doc = Article.database.get Article.design_doc.id
532
+ doc['views']['by_date'].should_not be_nil
533
+ end
534
+
535
+ it "should return the matching raw view result" do
536
+ view = Article.by_date :raw => true
537
+ view['rows'].length.should == 4
538
+ end
539
+
540
+ it "should not include non-Articles" do
541
+ Article.database.save_doc({"date" => 1})
542
+ view = Article.by_date :raw => true
543
+ view['rows'].length.should == 4
544
+ end
545
+
546
+ it "should return the matching objects (with default argument :descending => true)" do
547
+ articles = Article.by_date
548
+ articles.collect{|a|a.title}.should == @titles.reverse
549
+ end
550
+
551
+ it "should allow you to override default args" do
552
+ articles = Article.by_date :descending => false
553
+ articles.collect{|a|a.title}.should == @titles
554
+ end
555
+ end
556
+
557
+ describe "another model with a simple view" do
558
+ before(:all) do
559
+ Course.database.delete! rescue nil
560
+ @db = @cr.create_db(TESTDB) rescue nil
561
+ %w{aaa bbb ddd eee}.each do |title|
562
+ Course.new(:title => title).save
563
+ end
564
+ end
565
+ it "should make the design doc upon first query" do
566
+ Course.by_title
567
+ doc = Course.design_doc
568
+ doc['views']['all']['map'].should include('Course')
569
+ end
570
+ it "should can query via view" do
571
+ # register methods with method-missing, for local dispatch. method
572
+ # missing lookup table, no heuristics.
573
+ view = Course.view :by_title
574
+ designed = Course.by_title
575
+ view.should == designed
576
+ end
577
+ it "should get them" do
578
+ rs = Course.by_title
579
+ rs.length.should == 4
580
+ end
581
+ it "should yield" do
582
+ courses = []
583
+ rs = Course.by_title # remove me
584
+ Course.view(:by_title) do |course|
585
+ courses << course
586
+ end
587
+ courses[0]["doc"]["title"].should =='aaa'
588
+ end
589
+ end
590
+
591
+
592
+ describe "a ducktype view" do
593
+ before(:all) do
594
+ @id = @db.save_doc({:dept => true})['id']
595
+ end
596
+ it "should setup" do
597
+ duck = Course.get(@id) # from a different db
598
+ duck["dept"].should == true
599
+ end
600
+ it "should make the design doc" do
601
+ @as = Course.by_dept
602
+ @doc = Course.design_doc
603
+ @doc["views"]["by_dept"]["map"].should_not include("couchrest")
604
+ end
605
+ it "should not look for class" do |variable|
606
+ @as = Course.by_dept
607
+ @as[0]['_id'].should == @id
608
+ end
609
+ end
610
+
611
+ describe "a model with a compound key view" do
612
+ before(:all) do
613
+ written_at = Time.now - 24 * 3600 * 7
614
+ @titles = ["uniq one", "even more interesting", "less fun", "not junk"]
615
+ @user_ids = ["quentin", "aaron"]
616
+ @titles.each_with_index do |title,i|
617
+ u = i % 2
618
+ a = Article.new(:title => title, :user_id => @user_ids[u])
619
+ a.date = written_at
620
+ a.save
621
+ written_at += 24 * 3600
622
+ end
623
+ end
624
+ it "should create the design doc" do
625
+ Article.by_user_id_and_date rescue nil
626
+ doc = Article.design_doc
627
+ doc['views']['by_date'].should_not be_nil
628
+ end
629
+ it "should sort correctly" do
630
+ articles = Article.by_user_id_and_date
631
+ articles.collect{|a|a['user_id']}.should == ['aaron', 'aaron', 'quentin',
632
+ 'quentin']
633
+ articles[1].title.should == 'not junk'
634
+ end
635
+ it "should be queryable with couchrest options" do
636
+ articles = Article.by_user_id_and_date :limit => 1, :startkey => 'quentin'
637
+ articles.length.should == 1
638
+ articles[0].title.should == "even more interesting"
639
+ end
640
+ end
641
+
642
+ describe "with a custom view" do
643
+ before(:all) do
644
+ @titles = ["very uniq one", "even less interesting", "some fun",
645
+ "really junk", "crazy bob"]
646
+ @tags = ["cool", "lame"]
647
+ @titles.each_with_index do |title,i|
648
+ u = i % 2
649
+ a = Article.new(:title => title, :tags => [@tags[u]])
650
+ a.save
651
+ end
652
+ end
653
+ it "should be available raw" do
654
+ view = Article.by_tags :raw => true
655
+ view['rows'].length.should == 5
656
+ end
657
+
658
+ it "should be default to :reduce => false" do
659
+ ars = Article.by_tags
660
+ ars.first.tags.first.should == 'cool'
661
+ end
662
+
663
+ it "should be raw when reduce is true" do
664
+ view = Article.by_tags :reduce => true, :group => true
665
+ view['rows'].find{|r|r['key'] == 'cool'}['value'].should == 3
666
+ end
667
+ end
668
+
669
+ # TODO: moved to Design, delete
670
+ describe "adding a view" do
671
+ before(:each) do
672
+ Article.by_date
673
+ @design_docs = Article.database.documents :startkey => "_design/",
674
+ :endkey => "_design/\u9999"
675
+ end
676
+ it "should not create a design doc on view definition" do
677
+ Article.view_by :created_at
678
+ newdocs = Article.database.documents :startkey => "_design/",
679
+ :endkey => "_design/\u9999"
680
+ newdocs["rows"].length.should == @design_docs["rows"].length
681
+ end
682
+ it "should create a new design document on view access" do
683
+ Article.view_by :updated_at
684
+ Article.by_updated_at
685
+ newdocs = Article.database.documents :startkey => "_design/",
686
+ :endkey => "_design/\u9999"
687
+ # puts @design_docs.inspect
688
+ # puts newdocs.inspect
689
+ newdocs["rows"].length.should == @design_docs["rows"].length + 1
690
+ end
691
+ end
692
+
693
+ describe "with a lot of designs left around" do
694
+ before(:each) do
695
+ Article.by_date
696
+ Article.view_by :field
697
+ Article.by_field
698
+ end
699
+ it "should clean them up" do
700
+ Article.view_by :stream
701
+ Article.by_stream
702
+ ddocs = Article.all_design_doc_versions
703
+ ddocs["rows"].length.should > 1
704
+ Article.cleanup_design_docs!
705
+ ddocs = Article.all_design_doc_versions
706
+ ddocs["rows"].length.should == 1
707
+ end
708
+ end
709
+
710
+ describe "destroying an instance" do
711
+ before(:each) do
712
+ @obj = Basic.new
713
+ @obj.save.should == true
714
+ end
715
+ it "should return true" do
716
+ result = @obj.destroy
717
+ result.should == true
718
+ end
719
+ it "should be resavable" do
720
+ @obj.destroy
721
+ @obj.rev.should be_nil
722
+ @obj.id.should be_nil
723
+ @obj.save.should == true
724
+ end
725
+ it "should make it go away" do
726
+ @obj.destroy
727
+ lambda{Basic.get(@obj.id)}.should raise_error
728
+ end
729
+ end
730
+
731
+ describe "#has_attachment?" do
732
+ before(:each) do
733
+ @obj = Basic.new
734
+ @obj.save.should == true
735
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
736
+ @attachment_name = 'my_attachment'
737
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
738
+ end
739
+
740
+ it 'should return false if there is no attachment' do
741
+ @obj.has_attachment?('bogus').should be_false
742
+ end
743
+
744
+ it 'should return true if there is an attachment' do
745
+ @obj.has_attachment?(@attachment_name).should be_true
746
+ end
747
+
748
+ it 'should return true if an object with an attachment is reloaded' do
749
+ @obj.save.should be_true
750
+ reloaded_obj = Basic.get(@obj.id)
751
+ reloaded_obj.has_attachment?(@attachment_name).should be_true
752
+ end
753
+
754
+ it 'should return false if an attachment has been removed' do
755
+ @obj.delete_attachment(@attachment_name)
756
+ @obj.has_attachment?(@attachment_name).should be_false
757
+ end
758
+ end
759
+
760
+ describe "creating an attachment" do
761
+ before(:each) do
762
+ @obj = Basic.new
763
+ @obj.save.should == true
764
+ @file_ext = File.open(FIXTURE_PATH + '/attachments/test.html')
765
+ @file_no_ext = File.open(FIXTURE_PATH + '/attachments/README')
766
+ @attachment_name = 'my_attachment'
767
+ @content_type = 'media/mp3'
768
+ end
769
+
770
+ it "should create an attachment from file with an extension" do
771
+ @obj.create_attachment(:file => @file_ext, :name => @attachment_name)
772
+ @obj.save.should == true
773
+ reloaded_obj = Basic.get(@obj.id)
774
+ reloaded_obj['_attachments'][@attachment_name].should_not be_nil
775
+ end
776
+
777
+ it "should create an attachment from file without an extension" do
778
+ @obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
779
+ @obj.save.should == true
780
+ reloaded_obj = Basic.get(@obj.id)
781
+ reloaded_obj['_attachments'][@attachment_name].should_not be_nil
782
+ end
783
+
784
+ it 'should raise ArgumentError if :file is missing' do
785
+ lambda{ @obj.create_attachment(:name => @attachment_name) }.should raise_error
786
+ end
787
+
788
+ it 'should raise ArgumentError if :name is missing' do
789
+ lambda{ @obj.create_attachment(:file => @file_ext) }.should raise_error
790
+ end
791
+
792
+ it 'should set the content-type if passed' do
793
+ @obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
794
+ @obj['_attachments'][@attachment_name]['content-type'].should == @content_type
795
+ end
796
+ end
797
+
798
+ describe 'reading, updating, and deleting an attachment' do
799
+ before(:each) do
800
+ @obj = Basic.new
801
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
802
+ @attachment_name = 'my_attachment'
803
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
804
+ @obj.save.should == true
805
+ @file.rewind
806
+ @content_type = 'media/mp3'
807
+ end
808
+
809
+ it 'should read an attachment that exists' do
810
+ @obj.read_attachment(@attachment_name).should == @file.read
811
+ end
812
+
813
+ it 'should update an attachment that exists' do
814
+ file = File.open(FIXTURE_PATH + '/attachments/README')
815
+ @file.should_not == file
816
+ @obj.update_attachment(:file => file, :name => @attachment_name)
817
+ @obj.save
818
+ reloaded_obj = Basic.get(@obj.id)
819
+ file.rewind
820
+ reloaded_obj.read_attachment(@attachment_name).should_not == @file.read
821
+ reloaded_obj.read_attachment(@attachment_name).should == file.read
822
+ end
823
+
824
+ it 'should se the content-type if passed' do
825
+ file = File.open(FIXTURE_PATH + '/attachments/README')
826
+ @file.should_not == file
827
+ @obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
828
+ @obj['_attachments'][@attachment_name]['content-type'].should == @content_type
829
+ end
830
+
831
+ it 'should delete an attachment that exists' do
832
+ @obj.delete_attachment(@attachment_name)
833
+ @obj.save
834
+ lambda{Basic.get(@obj.id).read_attachment(@attachment_name)}.should raise_error
835
+ end
836
+ end
837
+
838
+ describe "#attachment_url" do
839
+ before(:each) do
840
+ @obj = Basic.new
841
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
842
+ @attachment_name = 'my_attachment'
843
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
844
+ @obj.save.should == true
845
+ end
846
+
847
+ it 'should return nil if attachment does not exist' do
848
+ @obj.attachment_url('bogus').should be_nil
849
+ end
850
+
851
+ it 'should return the attachment URL as specified by CouchDB HttpDocumentApi' do
852
+ @obj.attachment_url(@attachment_name).should == "#{Basic.database}/#{@obj.id}/#{@attachment_name}"
853
+ end
854
+ end
855
+ end