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