glasner-couchrest 0.2.2

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 (90) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +93 -0
  3. data/Rakefile +66 -0
  4. data/THANKS.md +18 -0
  5. data/examples/model/example.rb +144 -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 +189 -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 +313 -0
  20. data/lib/couchrest/core/design.rb +89 -0
  21. data/lib/couchrest/core/document.rb +96 -0
  22. data/lib/couchrest/core/response.rb +16 -0
  23. data/lib/couchrest/core/server.rb +88 -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/mixins.rb +4 -0
  28. data/lib/couchrest/mixins/attachments.rb +31 -0
  29. data/lib/couchrest/mixins/callbacks.rb +483 -0
  30. data/lib/couchrest/mixins/design_doc.rb +64 -0
  31. data/lib/couchrest/mixins/document_queries.rb +48 -0
  32. data/lib/couchrest/mixins/extended_attachments.rb +68 -0
  33. data/lib/couchrest/mixins/extended_document_mixins.rb +6 -0
  34. data/lib/couchrest/mixins/properties.rb +125 -0
  35. data/lib/couchrest/mixins/validation.rb +234 -0
  36. data/lib/couchrest/mixins/views.rb +168 -0
  37. data/lib/couchrest/monkeypatches.rb +119 -0
  38. data/lib/couchrest/more/casted_model.rb +28 -0
  39. data/lib/couchrest/more/extended_document.rb +217 -0
  40. data/lib/couchrest/more/property.rb +40 -0
  41. data/lib/couchrest/support/blank.rb +42 -0
  42. data/lib/couchrest/support/class.rb +191 -0
  43. data/lib/couchrest/validation/auto_validate.rb +163 -0
  44. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  45. data/lib/couchrest/validation/validation_errors.rb +118 -0
  46. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  47. data/lib/couchrest/validation/validators/confirmation_validator.rb +99 -0
  48. data/lib/couchrest/validation/validators/format_validator.rb +117 -0
  49. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  50. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  51. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  52. data/lib/couchrest/validation/validators/length_validator.rb +134 -0
  53. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  54. data/lib/couchrest/validation/validators/numeric_validator.rb +104 -0
  55. data/lib/couchrest/validation/validators/required_field_validator.rb +109 -0
  56. data/spec/couchrest/core/couchrest_spec.rb +201 -0
  57. data/spec/couchrest/core/database_spec.rb +745 -0
  58. data/spec/couchrest/core/design_spec.rb +131 -0
  59. data/spec/couchrest/core/document_spec.rb +311 -0
  60. data/spec/couchrest/core/server_spec.rb +35 -0
  61. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  62. data/spec/couchrest/helpers/streamer_spec.rb +23 -0
  63. data/spec/couchrest/more/casted_extended_doc_spec.rb +40 -0
  64. data/spec/couchrest/more/casted_model_spec.rb +98 -0
  65. data/spec/couchrest/more/extended_doc_attachment_spec.rb +130 -0
  66. data/spec/couchrest/more/extended_doc_spec.rb +509 -0
  67. data/spec/couchrest/more/extended_doc_view_spec.rb +207 -0
  68. data/spec/couchrest/more/property_spec.rb +130 -0
  69. data/spec/couchrest/support/class_spec.rb +59 -0
  70. data/spec/fixtures/attachments/README +3 -0
  71. data/spec/fixtures/attachments/couchdb.png +0 -0
  72. data/spec/fixtures/attachments/test.html +11 -0
  73. data/spec/fixtures/more/article.rb +34 -0
  74. data/spec/fixtures/more/card.rb +20 -0
  75. data/spec/fixtures/more/course.rb +14 -0
  76. data/spec/fixtures/more/event.rb +6 -0
  77. data/spec/fixtures/more/invoice.rb +17 -0
  78. data/spec/fixtures/more/person.rb +8 -0
  79. data/spec/fixtures/more/question.rb +6 -0
  80. data/spec/fixtures/more/service.rb +12 -0
  81. data/spec/fixtures/views/lib.js +3 -0
  82. data/spec/fixtures/views/test_view/lib.js +3 -0
  83. data/spec/fixtures/views/test_view/only-map.js +4 -0
  84. data/spec/fixtures/views/test_view/test-map.js +3 -0
  85. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  86. data/spec/spec.opts +6 -0
  87. data/spec/spec_helper.rb +26 -0
  88. data/utils/remap.rb +27 -0
  89. data/utils/subset.rb +30 -0
  90. metadata +219 -0
@@ -0,0 +1,109 @@
1
+ # Extracted from dm-validations 0.9.10
2
+ #
3
+ # Copyright (c) 2007 Guy van den Berg
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ module CouchRest
25
+ module Validation
26
+
27
+ ##
28
+ #
29
+ # @author Guy van den Berg
30
+ # @since 0.9
31
+ class RequiredFieldValidator < GenericValidator
32
+
33
+ def initialize(field_name, options={})
34
+ super
35
+ @field_name, @options = field_name, options
36
+ end
37
+
38
+ def call(target)
39
+ value = target.validation_property_value(field_name)
40
+ property = target.validation_property(field_name)
41
+ return true if present?(value, property)
42
+
43
+ error_message = @options[:message] || default_error(property)
44
+ add_error(target, error_message, field_name)
45
+
46
+ false
47
+ end
48
+
49
+ protected
50
+
51
+ # Boolean property types are considered present if non-nil.
52
+ # Other property types are considered present if non-blank.
53
+ # Non-properties are considered present if non-blank.
54
+ def present?(value, property)
55
+ boolean_type?(property) ? !value.nil? : !value.blank?
56
+ end
57
+
58
+ def default_error(property)
59
+ actual = boolean_type?(property) ? :nil : :blank
60
+ ValidationErrors.default_error_message(actual, field_name)
61
+ end
62
+
63
+ # Is +property+ a boolean property?
64
+ #
65
+ # Returns true for Boolean, ParanoidBoolean, TrueClass, etc. properties.
66
+ # Returns false for other property types.
67
+ # Returns false for non-properties.
68
+ def boolean_type?(property)
69
+ property ? property.type == TrueClass : false
70
+ end
71
+
72
+ end # class RequiredFieldValidator
73
+
74
+ module ValidatesPresent
75
+
76
+ ##
77
+ # Validates that the specified attribute is present.
78
+ #
79
+ # For most property types "being present" is the same as being "not
80
+ # blank" as determined by the attribute's #blank? method. However, in
81
+ # the case of Boolean, "being present" means not nil; i.e. true or
82
+ # false.
83
+ #
84
+ # @note
85
+ # dm-core's support lib adds the blank? method to many classes,
86
+ # @see lib/dm-core/support/blank.rb (dm-core) for more information.
87
+ #
88
+ # @example [Usage]
89
+ #
90
+ # class Page
91
+ #
92
+ # property :required_attribute, String
93
+ # property :another_required, String
94
+ # property :yet_again, String
95
+ #
96
+ # validates_present :required_attribute
97
+ # validates_present :another_required, :yet_again
98
+ #
99
+ # # a call to valid? will return false unless
100
+ # # all three attributes are !blank?
101
+ # end
102
+ def validates_present(*fields)
103
+ opts = opts_from_validator_args(fields)
104
+ add_validator_to_context(opts, fields, CouchRest::Validation::RequiredFieldValidator)
105
+ end
106
+
107
+ end # module ValidatesPresent
108
+ end # module Validation
109
+ end # module CouchRest
@@ -0,0 +1,201 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe CouchRest do
4
+
5
+ before(:each) do
6
+ @cr = CouchRest.new(COUCHHOST)
7
+ begin
8
+ @db = @cr.database(TESTDB)
9
+ @db.delete! rescue nil
10
+ end
11
+ end
12
+
13
+ after(:each) do
14
+ begin
15
+ @db.delete! rescue nil
16
+ end
17
+ end
18
+
19
+ describe "getting info" do
20
+ it "should list databases" do
21
+ @cr.databases.should be_an_instance_of(Array)
22
+ end
23
+ it "should get info" do
24
+ @cr.info["couchdb"].should == "Welcome"
25
+ @cr.info.class.should == Hash
26
+ end
27
+ end
28
+
29
+ it "should restart" do
30
+ @cr.restart!
31
+ end
32
+
33
+ it "should provide one-time access to uuids" do
34
+ @cr.next_uuid.should_not be_nil
35
+ end
36
+
37
+ describe "initializing a database" do
38
+ it "should return a db" do
39
+ db = @cr.database(TESTDB)
40
+ db.should be_an_instance_of(CouchRest::Database)
41
+ db.host.should == @cr.uri
42
+ end
43
+ end
44
+
45
+ describe "parsing urls" do
46
+ it "should parse just a dbname" do
47
+ db = CouchRest.parse "my-db"
48
+ db[:database].should == "my-db"
49
+ db[:host].should == "127.0.0.1:5984"
50
+ end
51
+ it "should parse a host and db" do
52
+ db = CouchRest.parse "127.0.0.1/my-db"
53
+ db[:database].should == "my-db"
54
+ db[:host].should == "127.0.0.1"
55
+ end
56
+ it "should parse a host and db with http" do
57
+ db = CouchRest.parse "http://127.0.0.1/my-db"
58
+ db[:database].should == "my-db"
59
+ db[:host].should == "127.0.0.1"
60
+ end
61
+ it "should parse a host with a port and db" do
62
+ db = CouchRest.parse "127.0.0.1:5555/my-db"
63
+ db[:database].should == "my-db"
64
+ db[:host].should == "127.0.0.1:5555"
65
+ end
66
+ it "should parse a host with a port and db with http" do
67
+ db = CouchRest.parse "http://127.0.0.1:5555/my-db"
68
+ db[:database].should == "my-db"
69
+ db[:host].should == "127.0.0.1:5555"
70
+ end
71
+ it "should parse just a host" do
72
+ db = CouchRest.parse "http://127.0.0.1:5555/"
73
+ db[:database].should be_nil
74
+ db[:host].should == "127.0.0.1:5555"
75
+ end
76
+ it "should parse just a host no slash" do
77
+ db = CouchRest.parse "http://127.0.0.1:5555"
78
+ db[:host].should == "127.0.0.1:5555"
79
+ db[:database].should be_nil
80
+ end
81
+ it "should get docid" do
82
+ db = CouchRest.parse "127.0.0.1:5555/my-db/my-doc"
83
+ db[:database].should == "my-db"
84
+ db[:host].should == "127.0.0.1:5555"
85
+ db[:doc].should == "my-doc"
86
+ end
87
+ it "should get docid with http" do
88
+ db = CouchRest.parse "http://127.0.0.1:5555/my-db/my-doc"
89
+ db[:database].should == "my-db"
90
+ db[:host].should == "127.0.0.1:5555"
91
+ db[:doc].should == "my-doc"
92
+ end
93
+
94
+ it "should parse a host and db" do
95
+ db = CouchRest.parse "127.0.0.1/my-db"
96
+ db[:database].should == "my-db"
97
+ db[:host].should == "127.0.0.1"
98
+ end
99
+ it "should parse a host and db with http" do
100
+ db = CouchRest.parse "http://127.0.0.1/my-db"
101
+ db[:database].should == "my-db"
102
+ db[:host].should == "127.0.0.1"
103
+ end
104
+ it "should parse a host with a port and db" do
105
+ db = CouchRest.parse "127.0.0.1:5555/my-db"
106
+ db[:database].should == "my-db"
107
+ db[:host].should == "127.0.0.1:5555"
108
+ end
109
+ it "should parse a host with a port and db with http" do
110
+ db = CouchRest.parse "http://127.0.0.1:5555/my-db"
111
+ db[:database].should == "my-db"
112
+ db[:host].should == "127.0.0.1:5555"
113
+ end
114
+ it "should parse just a host" do
115
+ db = CouchRest.parse "http://127.0.0.1:5555/"
116
+ db[:database].should be_nil
117
+ db[:host].should == "127.0.0.1:5555"
118
+ end
119
+ it "should parse just a host no slash" do
120
+ db = CouchRest.parse "http://127.0.0.1:5555"
121
+ db[:host].should == "127.0.0.1:5555"
122
+ db[:database].should be_nil
123
+ end
124
+ it "should get docid" do
125
+ db = CouchRest.parse "127.0.0.1:5555/my-db/my-doc"
126
+ db[:database].should == "my-db"
127
+ db[:host].should == "127.0.0.1:5555"
128
+ db[:doc].should == "my-doc"
129
+ end
130
+ it "should get docid with http" do
131
+ db = CouchRest.parse "http://127.0.0.1:5555/my-db/my-doc"
132
+ db[:database].should == "my-db"
133
+ db[:host].should == "127.0.0.1:5555"
134
+ db[:doc].should == "my-doc"
135
+ end
136
+ end
137
+
138
+ describe "easy initializing a database adapter" do
139
+ it "should be possible without an explicit CouchRest instantiation" do
140
+ db = CouchRest.database "http://127.0.0.1:5984/couchrest-test"
141
+ db.should be_an_instance_of(CouchRest::Database)
142
+ db.host.should == "127.0.0.1:5984"
143
+ end
144
+ # TODO add https support (need test environment...)
145
+ # it "should work with https" # do
146
+ # db = CouchRest.database "https://127.0.0.1:5984/couchrest-test"
147
+ # db.host.should == "https://127.0.0.1:5984"
148
+ # end
149
+ it "should not create the database automatically" do
150
+ db = CouchRest.database "http://127.0.0.1:5984/couchrest-test"
151
+ lambda{db.info}.should raise_error(RestClient::ResourceNotFound)
152
+ end
153
+ end
154
+
155
+ describe "ensuring the db exists" do
156
+ it "should be super easy" do
157
+ db = CouchRest.database! "http://127.0.0.1:5984/couchrest-test-2"
158
+ db.name.should == 'couchrest-test-2'
159
+ db.info["db_name"].should == 'couchrest-test-2'
160
+ end
161
+ end
162
+
163
+ describe "successfully creating a database" do
164
+ it "should start without a database" do
165
+ @cr.databases.should_not include(TESTDB)
166
+ end
167
+ it "should return the created database" do
168
+ db = @cr.create_db(TESTDB)
169
+ db.should be_an_instance_of(CouchRest::Database)
170
+ end
171
+ it "should create the database" do
172
+ db = @cr.create_db(TESTDB)
173
+ @cr.databases.should include(TESTDB)
174
+ end
175
+ end
176
+
177
+ describe "failing to create a database because the name is taken" do
178
+ before(:each) do
179
+ db = @cr.create_db(TESTDB)
180
+ end
181
+ it "should start with the test database" do
182
+ @cr.databases.should include(TESTDB)
183
+ end
184
+ it "should PUT the database and raise an error" do
185
+ lambda{
186
+ @cr.create_db(TESTDB)
187
+ }.should raise_error(RestClient::Request::RequestFailed)
188
+ end
189
+ end
190
+
191
+ describe "using a proxy for RestClient connections" do
192
+ it "should set proxy url for RestClient" do
193
+ CouchRest.proxy 'http://localhost:8888/'
194
+ proxy_uri = URI.parse(RestClient.proxy)
195
+ proxy_uri.host.should eql( 'localhost' )
196
+ proxy_uri.port.should eql( 8888 )
197
+ CouchRest.proxy nil
198
+ end
199
+ end
200
+
201
+ end
@@ -0,0 +1,745 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe CouchRest::Database do
4
+ before(:each) do
5
+ @cr = CouchRest.new(COUCHHOST)
6
+ @db = @cr.database(TESTDB)
7
+ @db.delete! rescue nil
8
+ @db = @cr.create_db(TESTDB) rescue nil
9
+ end
10
+
11
+ describe "map query with _temp_view in Javascript" do
12
+ before(:each) do
13
+ @db.bulk_save([
14
+ {"wild" => "and random"},
15
+ {"mild" => "yet local"},
16
+ {"another" => ["set","of","keys"]}
17
+ ])
18
+ @temp_view = {:map => "function(doc){for(var w in doc){ if(!w.match(/^_/))emit(w,doc[w])}}"}
19
+ end
20
+ it "should return the result of the temporary function" do
21
+ rs = @db.temp_view(@temp_view)
22
+ rs['rows'].select{|r|r['key'] == 'wild' && r['value'] == 'and random'}.length.should == 1
23
+ end
24
+ it "should work with a range" do
25
+ rs = @db.temp_view(@temp_view, :startkey => "b", :endkey => "z")
26
+ rs['rows'].length.should == 2
27
+ end
28
+ it "should work with a key" do
29
+ rs = @db.temp_view(@temp_view, :key => "wild")
30
+ rs['rows'].length.should == 1
31
+ end
32
+ it "should work with a limit" do
33
+ rs = @db.temp_view(@temp_view, :limit => 1)
34
+ rs['rows'].length.should == 1
35
+ end
36
+ it "should work with multi-keys" do
37
+ rs = @db.temp_view(@temp_view, :keys => ["another", "wild"])
38
+ rs['rows'].length.should == 2
39
+ end
40
+ end
41
+
42
+ describe "map/reduce query with _temp_view in Javascript" do
43
+ before(:each) do
44
+ @db.bulk_save([
45
+ {"beverage" => "beer", :count => 4},
46
+ {"beverage" => "beer", :count => 2},
47
+ {"beverage" => "tea", :count => 3}
48
+ ])
49
+ end
50
+ it "should return the result of the temporary function" do
51
+ rs = @db.temp_view(:map => "function(doc){emit(doc.beverage, doc.count)}", :reduce => "function(beverage,counts){return sum(counts)}")
52
+ # rs.should == 'x'
53
+ rs['rows'][0]['value'].should == 9
54
+ end
55
+ end
56
+
57
+ describe "saving a view" do
58
+ before(:each) do
59
+ @view = {'test' => {'map' => 'function(doc) {
60
+ if (doc.word && !/\W/.test(doc.word)) {
61
+ emit(doc.word,null);
62
+ }
63
+ }'}}
64
+ @db.save_doc({
65
+ "_id" => "_design/test",
66
+ :views => @view
67
+ })
68
+ end
69
+ it "should work properly" do
70
+ @db.bulk_save([
71
+ {"word" => "once"},
72
+ {"word" => "and again"}
73
+ ])
74
+ @db.view('test/test')['total_rows'].should == 1
75
+ end
76
+ it "should round trip" do
77
+ @db.get("_design/test")['views'].should == @view
78
+ end
79
+ end
80
+
81
+ describe "select from an existing view" do
82
+ before(:each) do
83
+ r = @db.save_doc({
84
+ "_id" => "_design/first",
85
+ :views => {
86
+ :test => {
87
+ :map => "function(doc){for(var w in doc){ if(!w.match(/^_/))emit(w,doc[w])}}"
88
+ }
89
+ }
90
+ })
91
+ @db.bulk_save([
92
+ {"wild" => "and random"},
93
+ {"mild" => "yet local"},
94
+ {"another" => ["set","of","keys"]}
95
+ ])
96
+ end
97
+ it "should have the view" do
98
+ @db.get('_design/first')['views']['test']['map'].should include("for(var w in doc)")
99
+ end
100
+ it "should list from the view" do
101
+ rs = @db.view('first/test')
102
+ rs['rows'].select{|r|r['key'] == 'wild' && r['value'] == 'and random'}.length.should == 1
103
+ end
104
+ it "should work with a range" do
105
+ rs = @db.view('first/test', :startkey => "b", :endkey => "z")
106
+ rs['rows'].length.should == 2
107
+ end
108
+ it "should work with a key" do
109
+ rs = @db.view('first/test', :key => "wild")
110
+ rs['rows'].length.should == 1
111
+ end
112
+ it "should work with a limit" do
113
+ rs = @db.view('first/test', :limit => 1)
114
+ rs['rows'].length.should == 1
115
+ end
116
+ it "should work with multi-keys" do
117
+ rs = @db.view('first/test', :keys => ["another", "wild"])
118
+ rs['rows'].length.should == 2
119
+ end
120
+ it "should accept a block" do
121
+ rows = []
122
+ rs = @db.view('first/test', :include_docs => true) do |row|
123
+ rows << row
124
+ end
125
+ rows.length.should == 4
126
+ rs["total_rows"].should == 3
127
+ end
128
+ end
129
+
130
+ describe "GET (document by id) when the doc exists" do
131
+ before(:each) do
132
+ @r = @db.save_doc({'lemons' => 'from texas', 'and' => 'spain'})
133
+ @docid = "http://example.com/stuff.cgi?things=and%20stuff"
134
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
135
+ end
136
+ it "should get the document" do
137
+ doc = @db.get(@r['id'])
138
+ doc['lemons'].should == 'from texas'
139
+ end
140
+ it "should work with a funky id" do
141
+ @db.get(@docid)['will-exist'].should == 'here'
142
+ end
143
+ end
144
+
145
+ describe "POST (adding bulk documents)" do
146
+ it "should add them without ids" do
147
+ rs = @db.bulk_save([
148
+ {"wild" => "and random"},
149
+ {"mild" => "yet local"},
150
+ {"another" => ["set","of","keys"]}
151
+ ])
152
+ rs['new_revs'].each do |r|
153
+ @db.get(r['id'])
154
+ end
155
+ end
156
+
157
+ it "should use uuids when ids aren't provided" do
158
+ @db.server.stub!(:next_uuid).and_return('asdf6sgadkfhgsdfusdf')
159
+
160
+ docs = [{'key' => 'value'}, {'_id' => 'totally-uniq'}]
161
+ id_docs = [{'key' => 'value', '_id' => 'asdf6sgadkfhgsdfusdf'}, {'_id' => 'totally-uniq'}]
162
+ CouchRest.should_receive(:post).with("http://127.0.0.1:5984/couchrest-test/_bulk_docs", {:docs => id_docs})
163
+
164
+ @db.bulk_save(docs)
165
+ end
166
+
167
+ it "should add them with uniq ids" do
168
+ rs = @db.bulk_save([
169
+ {"_id" => "oneB", "wild" => "and random"},
170
+ {"_id" => "twoB", "mild" => "yet local"},
171
+ {"another" => ["set","of","keys"]}
172
+ ])
173
+ rs['new_revs'].each do |r|
174
+ @db.get(r['id'])
175
+ end
176
+ end
177
+
178
+ it "in the case of an id conflict should not insert anything" do
179
+ @r = @db.save_doc({'lemons' => 'from texas', 'and' => 'how', "_id" => "oneB"})
180
+
181
+ lambda do
182
+ rs = @db.bulk_save([
183
+ {"_id" => "oneB", "wild" => "and random"},
184
+ {"_id" => "twoB", "mild" => "yet local"},
185
+ {"another" => ["set","of","keys"]}
186
+ ])
187
+ end.should raise_error(RestClient::RequestFailed)
188
+
189
+ lambda do
190
+ @db.get('twoB')
191
+ end.should raise_error(RestClient::ResourceNotFound)
192
+ end
193
+
194
+ it "should empty the bulk save cache if no documents are given" do
195
+ @db.save_doc({"_id" => "bulk_cache_1", "val" => "test"}, true)
196
+ lambda do
197
+ @db.get('bulk_cache_1')
198
+ end.should raise_error(RestClient::ResourceNotFound)
199
+ @db.bulk_save
200
+ @db.get("bulk_cache_1")["val"].should == "test"
201
+ end
202
+
203
+ it "should raise an error that is useful for recovery" do
204
+ @r = @db.save_doc({"_id" => "taken", "field" => "stuff"})
205
+ begin
206
+ rs = @db.bulk_save([
207
+ {"_id" => "taken", "wild" => "and random"},
208
+ {"_id" => "free", "mild" => "yet local"},
209
+ {"another" => ["set","of","keys"]}
210
+ ])
211
+ rescue RestClient::RequestFailed => e
212
+ # soon CouchDB will provide _which_ docs conflicted
213
+ JSON.parse(e.response.body)['error'].should == 'conflict'
214
+ end
215
+ end
216
+ end
217
+
218
+ describe "new document without an id" do
219
+ it "should start empty" do
220
+ @db.documents["total_rows"].should == 0
221
+ end
222
+ it "should create the document and return the id" do
223
+ r = @db.save_doc({'lemons' => 'from texas', 'and' => 'spain'})
224
+ r2 = @db.get(r['id'])
225
+ r2["lemons"].should == "from texas"
226
+ end
227
+ it "should use PUT with UUIDs" do
228
+ CouchRest.should_receive(:put).and_return({"ok" => true, "id" => "100", "rev" => "55"})
229
+ r = @db.save_doc({'just' => ['another document']})
230
+ end
231
+
232
+ end
233
+
234
+ describe "fetch_attachment" do
235
+ before do
236
+ @attach = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
237
+ @doc = {
238
+ "_id" => "mydocwithattachment",
239
+ "field" => ["some value"],
240
+ "_attachments" => {
241
+ "test.html" => {
242
+ "type" => "text/html",
243
+ "data" => @attach
244
+ }
245
+ }
246
+ }
247
+ @db.save_doc(@doc)
248
+ end
249
+
250
+ # Depreacated
251
+ # it "should get the attachment with the doc's _id" do
252
+ # @db.fetch_attachment("mydocwithattachment", "test.html").should == @attach
253
+ # end
254
+
255
+ it "should get the attachment with the doc itself" do
256
+ @db.fetch_attachment(@db.get('mydocwithattachment'), 'test.html').should == @attach
257
+ end
258
+ end
259
+
260
+ describe "PUT attachment from file" do
261
+ before(:each) do
262
+ filename = FIXTURE_PATH + '/attachments/couchdb.png'
263
+ @file = File.open(filename)
264
+ end
265
+ after(:each) do
266
+ @file.close
267
+ end
268
+ it "should save the attachment to a new doc" do
269
+ r = @db.put_attachment({'_id' => 'attach-this'}, 'couchdb.png', image = @file.read, {:content_type => 'image/png'})
270
+ r['ok'].should == true
271
+ doc = @db.get("attach-this")
272
+ attachment = @db.fetch_attachment(doc,"couchdb.png")
273
+ attachment.should == image
274
+ end
275
+ end
276
+
277
+ describe "PUT document with attachment" do
278
+ before(:each) do
279
+ @attach = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
280
+ doc = {
281
+ "_id" => "mydocwithattachment",
282
+ "field" => ["some value"],
283
+ "_attachments" => {
284
+ "test.html" => {
285
+ "type" => "text/html",
286
+ "data" => @attach
287
+ }
288
+ }
289
+ }
290
+ @db.save_doc(doc)
291
+ @doc = @db.get("mydocwithattachment")
292
+ end
293
+ it "should save and be indicated" do
294
+ @doc['_attachments']['test.html']['length'].should == @attach.length
295
+ end
296
+ it "should be there" do
297
+ attachment = @db.fetch_attachment(@doc,"test.html")
298
+ attachment.should == @attach
299
+ end
300
+ end
301
+
302
+ describe "PUT document with attachment stub" do
303
+ before(:each) do
304
+ @attach = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
305
+ doc = {
306
+ '_id' => 'mydocwithattachment',
307
+ 'field' => ['some_value'],
308
+ '_attachments' => {
309
+ 'test.html' => {
310
+ 'type' => 'text/html', 'data' => @attach
311
+ }
312
+ }
313
+ }
314
+ @db.save_doc(doc)
315
+ doc['_rev'].should_not be_nil
316
+ doc['field'] << 'another value'
317
+ @db.save_doc(doc)["ok"].should be_true
318
+ end
319
+
320
+ it 'should be there' do
321
+ doc = @db.get('mydocwithattachment')
322
+ attachment = @db.fetch_attachment(doc, 'test.html')
323
+ Base64.decode64(attachment).should == @attach
324
+ end
325
+ end
326
+
327
+ describe "PUT document with multiple attachments" do
328
+ before(:each) do
329
+ @attach = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
330
+ @attach2 = "<html><head><title>Other Doc</title></head><body><p>Has more words.</p></body></html>"
331
+ @doc = {
332
+ "_id" => "mydocwithattachment",
333
+ "field" => ["some value"],
334
+ "_attachments" => {
335
+ "test.html" => {
336
+ "type" => "text/html",
337
+ "data" => @attach
338
+ },
339
+ "other.html" => {
340
+ "type" => "text/html",
341
+ "data" => @attach2
342
+ }
343
+ }
344
+ }
345
+ @db.save_doc(@doc)
346
+ @doc = @db.get("mydocwithattachment")
347
+ end
348
+ it "should save and be indicated" do
349
+ @doc['_attachments']['test.html']['length'].should == @attach.length
350
+ @doc['_attachments']['other.html']['length'].should == @attach2.length
351
+ end
352
+ it "should be there" do
353
+ attachment = @db.fetch_attachment(@doc,"test.html")
354
+ attachment.should == @attach
355
+ end
356
+ it "should be there" do
357
+ attachment = @db.fetch_attachment(@doc,"other.html")
358
+ attachment.should == @attach2
359
+ end
360
+ end
361
+
362
+ describe "DELETE an attachment directly from the database" do
363
+ before(:each) do
364
+ doc = {
365
+ '_id' => 'mydocwithattachment',
366
+ '_attachments' => {
367
+ 'test.html' => {
368
+ 'type' => 'text/html',
369
+ 'data' => "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
370
+ }
371
+ }
372
+ }
373
+ @db.save_doc(doc)
374
+ @doc = @db.get('mydocwithattachment')
375
+ end
376
+ it "should delete the attachment" do
377
+ lambda { @db.fetch_attachment(@doc,'test.html') }.should_not raise_error
378
+ @db.delete_attachment(@doc, "test.html")
379
+ lambda { @db.fetch_attachment(@doc,'test.html') }.should raise_error(RestClient::ResourceNotFound)
380
+ end
381
+ end
382
+
383
+ describe "POST document with attachment (with funky name)" do
384
+ before(:each) do
385
+ @attach = "<html><head><title>My Funky Doc</title></head><body><p>Has words.</p></body></html>"
386
+ @doc = {
387
+ "field" => ["some other value"],
388
+ "_attachments" => {
389
+ "http://example.com/stuff.cgi?things=and%20stuff" => {
390
+ "type" => "text/html",
391
+ "data" => @attach
392
+ }
393
+ }
394
+ }
395
+ @docid = @db.save_doc(@doc)['id']
396
+ end
397
+ it "should save and be indicated" do
398
+ doc = @db.get(@docid)
399
+ doc['_attachments']['http://example.com/stuff.cgi?things=and%20stuff']['length'].should == @attach.length
400
+ end
401
+ it "should be there" do
402
+ doc = @db.get(@docid)
403
+ attachment = @db.fetch_attachment(doc,"http://example.com/stuff.cgi?things=and%20stuff")
404
+ attachment.should == @attach
405
+ end
406
+ end
407
+
408
+ describe "PUT (new document with url id)" do
409
+ it "should create the document" do
410
+ @docid = "http://example.com/stuff.cgi?things=and%20stuff"
411
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
412
+ lambda{@db.save_doc({'_id' => @docid})}.should raise_error(RestClient::Request::RequestFailed)
413
+ @db.get(@docid)['will-exist'].should == 'here'
414
+ end
415
+ end
416
+
417
+ describe "PUT (new document with id)" do
418
+ it "should start without the document" do
419
+ # r = @db.save_doc({'lemons' => 'from texas', 'and' => 'spain'})
420
+ @db.documents['rows'].each do |doc|
421
+ doc['id'].should_not == 'my-doc'
422
+ end
423
+ # should_not include({'_id' => 'my-doc'})
424
+ # this needs to be a loop over docs on content with the post
425
+ # or instead make it return something with a fancy <=> method
426
+ end
427
+ it "should create the document" do
428
+ @db.save_doc({'_id' => 'my-doc', 'will-exist' => 'here'})
429
+ lambda{@db.save_doc({'_id' => 'my-doc'})}.should raise_error(RestClient::Request::RequestFailed)
430
+ end
431
+ end
432
+
433
+ describe "PUT (existing document with rev)" do
434
+ before(:each) do
435
+ @db.save_doc({'_id' => 'my-doc', 'will-exist' => 'here'})
436
+ @doc = @db.get('my-doc')
437
+ @docid = "http://example.com/stuff.cgi?things=and%20stuff"
438
+ @db.save_doc({'_id' => @docid, 'now' => 'save'})
439
+ end
440
+ it "should start with the document" do
441
+ @doc['will-exist'].should == 'here'
442
+ @db.get(@docid)['now'].should == 'save'
443
+ end
444
+ it "should save with url id" do
445
+ doc = @db.get(@docid)
446
+ doc['yaml'] = ['json', 'word.']
447
+ @db.save_doc doc
448
+ @db.get(@docid)['yaml'].should == ['json', 'word.']
449
+ end
450
+ it "should fail to resave without the rev" do
451
+ @doc['them-keys'] = 'huge'
452
+ @doc['_rev'] = 'wrong'
453
+ # @db.save_doc(@doc)
454
+ lambda {@db.save_doc(@doc)}.should raise_error
455
+ end
456
+ it "should update the document" do
457
+ @doc['them-keys'] = 'huge'
458
+ @db.save_doc(@doc)
459
+ now = @db.get('my-doc')
460
+ now['them-keys'].should == 'huge'
461
+ end
462
+ end
463
+
464
+ describe "cached bulk save" do
465
+ it "stores documents in a database-specific cache" do
466
+ td = {"_id" => "btd1", "val" => "test"}
467
+ @db.save_doc(td, true)
468
+ @db.instance_variable_get("@bulk_save_cache").should == [td]
469
+
470
+ end
471
+
472
+ it "doesn't save to the database until the configured cache size is exceded" do
473
+ @db.bulk_save_cache_limit = 3
474
+ td1 = {"_id" => "td1", "val" => true}
475
+ td2 = {"_id" => "td2", "val" => 4}
476
+ @db.save_doc(td1, true)
477
+ @db.save_doc(td2, true)
478
+ lambda do
479
+ @db.get(td1["_id"])
480
+ end.should raise_error(RestClient::ResourceNotFound)
481
+ lambda do
482
+ @db.get(td2["_id"])
483
+ end.should raise_error(RestClient::ResourceNotFound)
484
+ td3 = {"_id" => "td3", "val" => "foo"}
485
+ @db.save_doc(td3, true)
486
+ @db.get(td1["_id"])["val"].should == td1["val"]
487
+ @db.get(td2["_id"])["val"].should == td2["val"]
488
+ @db.get(td3["_id"])["val"].should == td3["val"]
489
+ end
490
+
491
+ it "clears the bulk save cache the first time a non bulk save is requested" do
492
+ td1 = {"_id" => "blah", "val" => true}
493
+ td2 = {"_id" => "steve", "val" => 3}
494
+ @db.bulk_save_cache_limit = 50
495
+ @db.save_doc(td1, true)
496
+ lambda do
497
+ @db.get(td1["_id"])
498
+ end.should raise_error(RestClient::ResourceNotFound)
499
+ @db.save_doc(td2)
500
+ @db.get(td1["_id"])["val"].should == td1["val"]
501
+ @db.get(td2["_id"])["val"].should == td2["val"]
502
+ end
503
+ end
504
+
505
+ describe "DELETE existing document" do
506
+ before(:each) do
507
+ @r = @db.save_doc({'lemons' => 'from texas', 'and' => 'spain'})
508
+ @docid = "http://example.com/stuff.cgi?things=and%20stuff"
509
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
510
+ end
511
+ it "should work" do
512
+ doc = @db.get(@r['id'])
513
+ doc['and'].should == 'spain'
514
+ @db.delete_doc doc
515
+ lambda{@db.get @r['id']}.should raise_error
516
+ end
517
+ it "should work with uri id" do
518
+ doc = @db.get(@docid)
519
+ @db.delete_doc doc
520
+ lambda{@db.get @docid}.should raise_error
521
+ end
522
+ it "should fail without an _id" do
523
+ lambda{@db.delete_doc({"not"=>"a real doc"})}.should raise_error(ArgumentError)
524
+ end
525
+ it "should defer actual deletion when using bulk save" do
526
+ doc = @db.get(@docid)
527
+ @db.delete_doc doc, true
528
+ lambda{@db.get @docid}.should_not raise_error
529
+ @db.bulk_save
530
+ lambda{@db.get @docid}.should raise_error
531
+ end
532
+
533
+ end
534
+
535
+ describe "COPY existing document" do
536
+ before :each do
537
+ @r = @db.save_doc({'artist' => 'Zappa', 'title' => 'Muffin Man'})
538
+ @docid = 'tracks/zappa/muffin-man'
539
+ @doc = @db.get(@r['id'])
540
+ end
541
+ describe "to a new location" do
542
+ it "should work" do
543
+ @db.copy_doc @doc, @docid
544
+ newdoc = @db.get(@docid)
545
+ newdoc['artist'].should == 'Zappa'
546
+ end
547
+ it "should fail without an _id" do
548
+ lambda{@db.copy({"not"=>"a real doc"})}.should raise_error(ArgumentError)
549
+ end
550
+ end
551
+ describe "to an existing location" do
552
+ before :each do
553
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
554
+ end
555
+ it "should fail without a rev" do
556
+ lambda{@db.copy_doc @doc, @docid}.should raise_error(RestClient::RequestFailed)
557
+ end
558
+ it "should succeed with a rev" do
559
+ @to_be_overwritten = @db.get(@docid)
560
+ @db.copy_doc @doc, "#{@docid}?rev=#{@to_be_overwritten['_rev']}"
561
+ newdoc = @db.get(@docid)
562
+ newdoc['artist'].should == 'Zappa'
563
+ end
564
+ it "should succeed given the doc to overwrite" do
565
+ @to_be_overwritten = @db.get(@docid)
566
+ @db.copy_doc @doc, @to_be_overwritten
567
+ newdoc = @db.get(@docid)
568
+ newdoc['artist'].should == 'Zappa'
569
+ end
570
+ end
571
+ end
572
+
573
+ describe "MOVE existing document" do
574
+ before :each do
575
+ @r = @db.save_doc({'artist' => 'Zappa', 'title' => 'Muffin Man'})
576
+ @docid = 'tracks/zappa/muffin-man'
577
+ @doc = @db.get(@r['id'])
578
+ end
579
+ describe "to a new location" do
580
+ it "should work" do
581
+ @db.move_doc @doc, @docid
582
+ newdoc = @db.get(@docid)
583
+ newdoc['artist'].should == 'Zappa'
584
+ lambda {@db.get(@r['id'])}.should raise_error(RestClient::ResourceNotFound)
585
+ end
586
+ it "should fail without an _id or _rev" do
587
+ lambda{@db.move({"not"=>"a real doc"})}.should raise_error(ArgumentError)
588
+ lambda{@db.move({"_id"=>"not a real doc"})}.should raise_error(ArgumentError)
589
+ end
590
+ end
591
+ describe "to an existing location" do
592
+ before :each do
593
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
594
+ end
595
+ it "should fail without a rev" do
596
+ lambda{@db.move_doc @doc, @docid}.should raise_error(RestClient::RequestFailed)
597
+ lambda{@db.get(@r['id'])}.should_not raise_error
598
+ end
599
+ it "should succeed with a rev" do
600
+ @to_be_overwritten = @db.get(@docid)
601
+ @db.move_doc @doc, "#{@docid}?rev=#{@to_be_overwritten['_rev']}"
602
+ newdoc = @db.get(@docid)
603
+ newdoc['artist'].should == 'Zappa'
604
+ lambda {@db.get(@r['id'])}.should raise_error(RestClient::ResourceNotFound)
605
+ end
606
+ it "should succeed given the doc to overwrite" do
607
+ @to_be_overwritten = @db.get(@docid)
608
+ @db.move_doc @doc, @to_be_overwritten
609
+ newdoc = @db.get(@docid)
610
+ newdoc['artist'].should == 'Zappa'
611
+ lambda {@db.get(@r['id'])}.should raise_error(RestClient::ResourceNotFound)
612
+ end
613
+ end
614
+ end
615
+
616
+
617
+ it "should list documents" do
618
+ 5.times do
619
+ @db.save_doc({'another' => 'doc', 'will-exist' => 'anywhere'})
620
+ end
621
+ ds = @db.documents
622
+ ds['rows'].should be_an_instance_of(Array)
623
+ ds['rows'][0]['id'].should_not be_nil
624
+ ds['total_rows'].should == 5
625
+ end
626
+
627
+ describe "documents / _all_docs" do
628
+ before(:each) do
629
+ 9.times do |i|
630
+ @db.save_doc({'_id' => "doc#{i}",'another' => 'doc', 'will-exist' => 'here'})
631
+ end
632
+ end
633
+ it "should list documents with keys and such" do
634
+ ds = @db.documents
635
+ ds['rows'].should be_an_instance_of(Array)
636
+ ds['rows'][0]['id'].should == "doc0"
637
+ ds['total_rows'].should == 9
638
+ end
639
+ it "should take query params" do
640
+ ds = @db.documents(:startkey => 'doc0', :endkey => 'doc3')
641
+ ds['rows'].length.should == 4
642
+ ds = @db.documents(:key => 'doc0')
643
+ ds['rows'].length.should == 1
644
+ end
645
+ it "should work with multi-key" do
646
+ rs = @db.documents :keys => ["doc0", "doc7"]
647
+ rs['rows'].length.should == 2
648
+ end
649
+ it "should work with include_docs" do
650
+ ds = @db.documents(:startkey => 'doc0', :endkey => 'doc3', :include_docs => true)
651
+ ds['rows'][0]['doc']['another'].should == "doc"
652
+ end
653
+ end
654
+
655
+
656
+ describe "compacting a database" do
657
+ it "should compact the database" do
658
+ db = @cr.database('couchrest-test')
659
+ # r =
660
+ db.compact!
661
+ # r['ok'].should == true
662
+ end
663
+ end
664
+
665
+ describe "deleting a database" do
666
+ it "should start with the test database" do
667
+ @cr.databases.should include('couchrest-test')
668
+ end
669
+ it "should delete the database" do
670
+ db = @cr.database('couchrest-test')
671
+ # r =
672
+ db.delete!
673
+ # r['ok'].should == true
674
+ @cr.databases.should_not include('couchrest-test')
675
+ end
676
+ end
677
+
678
+ describe "replicating a database" do
679
+ before do
680
+ @db.save_doc({'_id' => 'test_doc', 'some-value' => 'foo'})
681
+ @other_db = @cr.database 'couchrest-test-replication'
682
+ @other_db.delete! rescue nil
683
+ @other_db = @cr.create_db 'couchrest-test-replication'
684
+ end
685
+
686
+ describe "via pulling" do
687
+ before do
688
+ @other_db.replicate_from @db
689
+ end
690
+
691
+ it "contains the document from the original database" do
692
+ doc = @other_db.get('test_doc')
693
+ doc['some-value'].should == 'foo'
694
+ end
695
+ end
696
+
697
+ describe "via pushing" do
698
+ before do
699
+ @db.replicate_to @other_db
700
+ end
701
+
702
+ it "copies the document to the other database" do
703
+ doc = @other_db.get('test_doc')
704
+ doc['some-value'].should == 'foo'
705
+ end
706
+ end
707
+ end
708
+
709
+ describe "creating a database" do
710
+ before(:each) do
711
+ @db = @cr.database('couchrest-test-db_to_create')
712
+ @db.delete! if @cr.databases.include?('couchrest-test-db_to_create')
713
+ end
714
+
715
+ it "should just work fine" do
716
+ @cr.databases.should_not include('couchrest-test-db_to_create')
717
+ @db.create!
718
+ @cr.databases.should include('couchrest-test-db_to_create')
719
+ end
720
+ end
721
+
722
+ describe "recreating a database" do
723
+ before(:each) do
724
+ @db = @cr.database('couchrest-test-db_to_create')
725
+ @db2 = @cr.database('couchrest-test-db_to_recreate')
726
+ @cr.databases.include?(@db.name) ? nil : @db.create!
727
+ @cr.databases.include?(@db2.name) ? @db2.delete! : nil
728
+ end
729
+
730
+ it "should drop and recreate a database" do
731
+ @cr.databases.should include(@db.name)
732
+ @db.recreate!
733
+ @cr.databases.should include(@db.name)
734
+ end
735
+
736
+ it "should recreate a db even tho it doesn't exist" do
737
+ @cr.databases.should_not include(@db2.name)
738
+ @db2.recreate!
739
+ @cr.databases.should include(@db2.name)
740
+ end
741
+
742
+ end
743
+
744
+
745
+ end