jgre-couchrest 0.12.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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