akdubya-cushion 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +176 -0
- data/README.rdoc +226 -0
- data/Rakefile +65 -0
- data/lib/cushion.rb +97 -0
- data/lib/cushion/core_ext.rb +27 -0
- data/lib/cushion/database.rb +310 -0
- data/lib/cushion/design.rb +33 -0
- data/lib/cushion/document.rb +117 -0
- data/lib/cushion/server.rb +208 -0
- data/spec/cushion_spec.rb +9 -0
- data/spec/database_spec.rb +223 -0
- data/spec/helpers.rb +2 -0
- data/spec/server_spec.rb +119 -0
- metadata +75 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
module Cushion
|
2
|
+
class Response < Hash
|
3
|
+
def initialize(keys = {})
|
4
|
+
keys.each do |k,v|
|
5
|
+
self[k.to_s] = v
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def []=(key, value)
|
10
|
+
super(key.to_s, value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
super(key.to_s)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Document < Response
|
19
|
+
attr_accessor :database
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def use_database(db)
|
23
|
+
@database = db
|
24
|
+
end
|
25
|
+
|
26
|
+
def database
|
27
|
+
@database
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def id
|
32
|
+
self[:_id]
|
33
|
+
end
|
34
|
+
|
35
|
+
def rev
|
36
|
+
self[:_rev]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns true if this document has never been saved
|
40
|
+
def new_document?
|
41
|
+
!rev
|
42
|
+
end
|
43
|
+
|
44
|
+
# Saves this document to the database.
|
45
|
+
def save
|
46
|
+
result = database.save_doc(self)
|
47
|
+
if result['ok']
|
48
|
+
self[:_rev] = result['rev']
|
49
|
+
self[:_id] = result['id']
|
50
|
+
end
|
51
|
+
result['ok']
|
52
|
+
end
|
53
|
+
|
54
|
+
# Deletes this document from the database.
|
55
|
+
def destroy
|
56
|
+
result = database.delete_doc(id, rev)
|
57
|
+
if result['ok']
|
58
|
+
self[:_rev] = nil
|
59
|
+
self[:_id] = nil
|
60
|
+
end
|
61
|
+
result['ok']
|
62
|
+
end
|
63
|
+
|
64
|
+
# Reloads this document from the database.
|
65
|
+
def reload
|
66
|
+
result = database.open_doc(id)
|
67
|
+
initialize(result)
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
# Copies the document to a new id. If +dest_id+ currently exists then
|
72
|
+
# +dest_rev+ must be provided.
|
73
|
+
def copy(dest_id, dest_rev = nil)
|
74
|
+
result = database.copy_doc(id, dest_id, :dest_rev => dest_rev)
|
75
|
+
result['ok']
|
76
|
+
end
|
77
|
+
|
78
|
+
# Moves the document to a new id. If +dest_id+ currently exists then
|
79
|
+
# +dest_rev+ must be provided.
|
80
|
+
def move(dest_id, dest_rev = nil)
|
81
|
+
result = database.move_doc(id, dest_id, rev, :dest_rev => dest_rev)
|
82
|
+
result['ok']
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns this document's database.
|
86
|
+
def database
|
87
|
+
@database || self.class.database
|
88
|
+
end
|
89
|
+
|
90
|
+
# Opens the attachment identified by +filename+. Returns an IO object.
|
91
|
+
def open_attachment(filename)
|
92
|
+
database.open_attachment(id, filename)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Attaches a file to this document under +filename+.
|
96
|
+
def save_attachment(filename, data, options={})
|
97
|
+
result = database.save_attachment(id, rev, filename, data, options)
|
98
|
+
self[:_rev] = result['rev']
|
99
|
+
result['ok']
|
100
|
+
end
|
101
|
+
|
102
|
+
# Deletes the attachment identified by +filename+.
|
103
|
+
def delete_attachment(filename)
|
104
|
+
result = database.delete_attachment(id, rev, filename)
|
105
|
+
self[:_rev] = result['rev']
|
106
|
+
result['ok']
|
107
|
+
end
|
108
|
+
|
109
|
+
# Renames an attachment. +oldname+ is the name of the existing attachment
|
110
|
+
# and +newname+ is the desired name.
|
111
|
+
def rename_attachment(oldname, newname)
|
112
|
+
result = database.rename_attachment(id, rev, oldname, newname)
|
113
|
+
self[:_rev] = result['rev']
|
114
|
+
result['ok']
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module Cushion
|
4
|
+
class Server
|
5
|
+
attr_accessor :uri, :uuid_batch_count
|
6
|
+
|
7
|
+
# Initialize a Cushion::Server to the uri at +server+.
|
8
|
+
def initialize(server = DEFAULT_COUCH_HOST, uuid_batch_count = 1000)
|
9
|
+
@uri = server
|
10
|
+
@uuid_batch_count = uuid_batch_count
|
11
|
+
end
|
12
|
+
|
13
|
+
# Retrieves the server's welcome message.
|
14
|
+
def info(headers = {})
|
15
|
+
get('', headers)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Retrieves a list of running tasks on the server.
|
19
|
+
def active_tasks(headers = {})
|
20
|
+
get("_active_tasks", headers)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Retrieves a list of all databases on the server.
|
24
|
+
def all_dbs(headers = {})
|
25
|
+
get("_all_dbs", headers)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Fetches the configuration information for this server.
|
29
|
+
def config(headers = {})
|
30
|
+
get("_config", headers)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets a configuration option identified by +section+ and +option+.
|
34
|
+
def set_config(section, option, value)
|
35
|
+
put("_config/#{section}/#{option}", value, :accept => "text/plain")
|
36
|
+
end
|
37
|
+
|
38
|
+
# Replicates +source+ database to +target+ database. +source+ and +target+
|
39
|
+
# may be either local database names (for local replication) or fully
|
40
|
+
# qualified urls (for remote replication).
|
41
|
+
def replicate(source, target, headers = {})
|
42
|
+
post("_replicate", {:source => source.to_s, :target => target.to_s}, headers)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Restarts the server.
|
46
|
+
def restart(headers = {})
|
47
|
+
post("_restart", nil, headers)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Fetches stats information for this server.
|
51
|
+
def stats(headers = {})
|
52
|
+
get("_stats", headers)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Creates a database on this server with the provided +name+.
|
56
|
+
def create(name, headers = {})
|
57
|
+
db(name).create(headers)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Deletes the database identified by +name+ from this server.
|
61
|
+
def delete(name, headers = {})
|
62
|
+
db(name).delete(headers)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Deletes and re-creates the database identified by +name+.
|
66
|
+
# Returns a Cushion::Database.
|
67
|
+
def recreate(name)
|
68
|
+
rdb = db(name)
|
69
|
+
begin
|
70
|
+
rdb.delete
|
71
|
+
rescue RestClient::ResourceNotFound
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
rdb.create
|
75
|
+
rdb
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns a Cushion::Database identified by +name+. This method does
|
79
|
+
# not attempt to create the database if it does not already exist on the
|
80
|
+
# server.
|
81
|
+
def [](name)
|
82
|
+
db(name)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns a Cushion::Database identified by +name+. This method does
|
86
|
+
# not attempt to create the database if it does not already exist on the
|
87
|
+
# server.
|
88
|
+
def db(name)
|
89
|
+
Cushion::Database.new(self, name)
|
90
|
+
end
|
91
|
+
alias_method :database, :db
|
92
|
+
|
93
|
+
# Creates a database on this server with the provided +name+ if it doesn't
|
94
|
+
# already exist. Returns a Cushion::Database.
|
95
|
+
def db!(name)
|
96
|
+
ndb = db(name)
|
97
|
+
ndb.create rescue nil
|
98
|
+
ndb
|
99
|
+
end
|
100
|
+
alias_method :database!, :db!
|
101
|
+
|
102
|
+
# Issues a HEAD request to the CouchDB server.
|
103
|
+
def head(path, headers = {})
|
104
|
+
RestClient.head("#{@uri}/#{path}", headers).headers
|
105
|
+
end
|
106
|
+
|
107
|
+
# Issues a GET request to the CouchDB server. Returns a parsed response body
|
108
|
+
# if the +accept+ options is set to 'application/json' (the default), otherwise
|
109
|
+
# returns the raw RestClient response body.
|
110
|
+
def get(path, headers = {})
|
111
|
+
defaults = { :accept => "application/json" }
|
112
|
+
parse_response(RestClient.get("#{@uri}/#{path}", defaults.merge(headers)))
|
113
|
+
end
|
114
|
+
|
115
|
+
# Issues a POST request to the CouchDB server. Parses the request body if
|
116
|
+
# the +content_type+ option is set to 'application/json' (the default).
|
117
|
+
# Returns a parsed response body if the +accept+ options is set to
|
118
|
+
# 'application/json' (the default), otherwise returns the raw
|
119
|
+
# RestClient response body.
|
120
|
+
def post(path, body, headers = {})
|
121
|
+
defaults = { :accept => "application/json", :content_type => "application/json" }
|
122
|
+
opts = defaults.merge(headers)
|
123
|
+
parse_response(RestClient.post("#{@uri}/#{path}", construct_payload(body, opts), opts))
|
124
|
+
end
|
125
|
+
|
126
|
+
# Issues a PUT request to the CouchDB server. Parses the request body if
|
127
|
+
# the +content_type+ option is set to 'application/json' (the default).
|
128
|
+
# Returns a parsed response body if the +accept+ option is set to
|
129
|
+
# 'application/json' (the default), otherwise returns the raw
|
130
|
+
# RestClient response body.
|
131
|
+
def put(path, body, headers = {})
|
132
|
+
defaults = { :accept => "application/json", :content_type => "application/json" }
|
133
|
+
opts = defaults.merge(headers)
|
134
|
+
parse_response(RestClient.put("#{@uri}/#{path}", construct_payload(body, opts), opts))
|
135
|
+
end
|
136
|
+
|
137
|
+
# Issues a DELETE request to the CouchDB server. Returns a parsed response body
|
138
|
+
# if the +accept+ options is set to 'application/json' (the default), otherwise
|
139
|
+
# returns the raw RestClient response body.
|
140
|
+
def delete(path, headers = {})
|
141
|
+
defaults = { :accept => "application/json" }
|
142
|
+
parse_response(RestClient.delete("#{@uri}/#{path}", defaults.merge(headers)))
|
143
|
+
end
|
144
|
+
|
145
|
+
# Issues a COPY request to the CouchDB server, copying a document from +source+
|
146
|
+
# to +destination+. Returns a parsed response body if the +accept+ options
|
147
|
+
# is set to 'application/json' (the default), otherwise returns the raw
|
148
|
+
# RestClient response body.
|
149
|
+
def copy(source, destination, headers = {})
|
150
|
+
defaults = { :accept => "application/json", 'Destination' => destination }
|
151
|
+
parse_response(RestClient.copy("#{@uri}/#{source}", defaults.merge(headers)))
|
152
|
+
end
|
153
|
+
|
154
|
+
# Issues a MOVE request to the CouchDB server, moving a document from +source+
|
155
|
+
# to +destination+. Returns a parsed response body if the +accept+ options
|
156
|
+
# is set to 'application/json' (the default), otherwise returns the raw
|
157
|
+
# RestClient response body.
|
158
|
+
def move(source, destination, headers = {})
|
159
|
+
defaults = { :accept => "application/json", 'Destination' => destination }
|
160
|
+
parse_response(RestClient.move("#{@uri}/#{source}", defaults.merge(headers)))
|
161
|
+
end
|
162
|
+
|
163
|
+
def request(verb, path, params)
|
164
|
+
RestClient::Request.execute(
|
165
|
+
:method => verb,
|
166
|
+
:url => "#{@uri}/#{path}",
|
167
|
+
:payload => params[:body],
|
168
|
+
:headers => params[:headers]
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Opens the attachment located at +path+. Returns a Tempfile.
|
173
|
+
def open_attachment(path)
|
174
|
+
open("#{@uri}/#{path}")
|
175
|
+
rescue OpenURI::HTTPError
|
176
|
+
raise RestClient::ResourceNotFound
|
177
|
+
end
|
178
|
+
|
179
|
+
# Retrives the next unused UUID from CouchDB. Provide a +count+ to set the number
|
180
|
+
# of UUIDs cached by this server instance.
|
181
|
+
def next_uuid(count = @uuid_batch_count)
|
182
|
+
@uuids ||= []
|
183
|
+
if @uuids.empty?
|
184
|
+
@uuids = get("_uuids?count=#{count}")["uuids"]
|
185
|
+
end
|
186
|
+
@uuids.pop
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
def parse_response(result)
|
192
|
+
if result.headers[:content_type] == "application/json"
|
193
|
+
JSON.parse(result)
|
194
|
+
else
|
195
|
+
result
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def construct_payload(body, opts)
|
200
|
+
return nil unless body
|
201
|
+
if opts[:content_type] == "application/json"
|
202
|
+
body.to_json
|
203
|
+
else
|
204
|
+
body
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helpers'
|
2
|
+
|
3
|
+
describe "Cushion::Database" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@db = Cushion.db!('http://127.0.0.1:5984/cushion_test')
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
@db.delete rescue nil
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "Operations" do
|
14
|
+
|
15
|
+
it "should properly initialize a database instance" do
|
16
|
+
badname = Cushion::Database.new(Cushion.new, "my/database")
|
17
|
+
badname.name.should == "my%2Fdatabase"
|
18
|
+
lambda { Cushion::Database.new( :foo, "mydb" ) }.should.raise ArgumentError
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should create and delete a database" do
|
22
|
+
newdb = Cushion.db('http://127.0.0.1:5984/foo_test')
|
23
|
+
newdb.create["ok"].should.be.true
|
24
|
+
newdb.delete["ok"].should.be.true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should retrieve database info" do
|
28
|
+
@db.info["db_name"].should == "cushion_test"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should compact a database" do
|
32
|
+
@db.save_doc("foo" => "bar")
|
33
|
+
@db.compact["ok"].should.be.true
|
34
|
+
# Finish compaction before moving on
|
35
|
+
while @db.server.active_tasks != [] do
|
36
|
+
sleep(0.5)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should pass requests to external handlers" do
|
41
|
+
lambda { @db.external(:get, "stats") }.should.not.raise
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "Saving" do
|
47
|
+
|
48
|
+
it "should save a document" do
|
49
|
+
@db.save_doc("_id" => "123")["ok"].should.be.true
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should automatically generate an id if none provided on save" do
|
53
|
+
saved = @db.save_doc("foo" => "bar")
|
54
|
+
saved["ok"].should.be.true
|
55
|
+
saved["rev"].should.not.be.nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should bulk save documents" do
|
59
|
+
count = @db.info["doc_count"]
|
60
|
+
@db.bulk_docs([{"_id" => "456"},{"_id" => "789"}])["ok"].should.be.true
|
61
|
+
@db.info["doc_count"].should == count + 2
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "Deleting" do
|
67
|
+
|
68
|
+
before do
|
69
|
+
@db.save_doc("_id" => "123")
|
70
|
+
@db.save_doc("_id" => "456")
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should delete a document" do
|
74
|
+
doc = @db.open_doc("123")
|
75
|
+
@db.delete_doc("123", doc['_rev'])["ok"].should.be.true
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should bulk delete documents" do
|
79
|
+
count = @db.info["doc_count"]
|
80
|
+
docs = @db.all_docs(:keys => ["123", "456"], :include_docs => true)
|
81
|
+
@db.bulk_delete(docs["rows"].map{ |row| row["doc"] })["ok"].should.be.true
|
82
|
+
@db.info["doc_count"].should == count - 2
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "Copying" do
|
88
|
+
|
89
|
+
before do
|
90
|
+
@abc = @db.save_doc("_id" => "abc")
|
91
|
+
@ghi = @db.save_doc("_id" => "ghi")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should copy an existing doc to a new/existing doc" do
|
95
|
+
res = @db.copy_doc("abc", "def")
|
96
|
+
res["id"].should == "def"
|
97
|
+
@db.copy_doc("abc", "def", :dest_rev => res["rev"])["ok"].should.be.true
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should move an existing doc to a new/existing doc" do
|
101
|
+
@db.move_doc("abc", "def", @abc["rev"])["ok"].should.be.true
|
102
|
+
lambda { @db.open_doc("abc") }.should.raise RestClient::ResourceNotFound
|
103
|
+
doc = @db.open_doc("def")
|
104
|
+
@db.move_doc("def", "ghi", doc["_rev"], :dest_rev => @ghi["rev"])["ok"].should.be.true
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "Reading" do
|
110
|
+
|
111
|
+
before do
|
112
|
+
@db.save_doc("_id" => "123")
|
113
|
+
@db.save_doc("_id" => "456")
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should open a document" do
|
117
|
+
@db.open_doc("123")["_id"].should == "123"
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should load a Cushion::Document" do
|
121
|
+
@db.doc("123").should.be.kind_of Cushion::Document
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should retrieve all documents" do
|
125
|
+
@db.all_docs["total_rows"].should == 2
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should retrieve all documents by keys" do
|
129
|
+
@db.all_docs(:keys => ["123"])["rows"].length.should == 1
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "Views" do
|
135
|
+
|
136
|
+
before do
|
137
|
+
@db.save_doc("_id" => "gomer", "name" => "gomer pyle")
|
138
|
+
@db.save_doc("_id" => "vince", "name" => "vince carter")
|
139
|
+
@view = {:map => "function(doc){emit(doc.name,null)}"}
|
140
|
+
@db.save_doc("_id" => "_design/foo", "views" => { "bar" => @view })
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should retrieve docs from temp views" do
|
144
|
+
@db.temp_view(@view, :key => "gomer pyle")["rows"].length.should == 1
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should retrieve docs from temp views by keys" do
|
148
|
+
@db.temp_view(@view, :keys => ["vince carter"])["rows"].length.should == 1
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should retrieve docs from regular views" do
|
152
|
+
@db.view("foo/bar", :key => "gomer pyle")["rows"].length.should == 1
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should retrieve docs from regular views by keys" do
|
156
|
+
@db.view("foo/bar", :keys => ["vince carter"])["rows"].length.should == 1
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should retrieve a doc from a show function" do
|
160
|
+
design = @db.open_doc("_design/foo")
|
161
|
+
design["shows"] = { "people" => "function(doc, req) { var response = {'code':200, 'headers':{}, 'body':'Hello World'}; return response; }" }
|
162
|
+
@db.save_doc(design)
|
163
|
+
@db.show("foo", "people", "gomer").should == "Hello World"
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should retrieve docs from a list function" do
|
167
|
+
design = @db.open_doc("_design/foo")
|
168
|
+
design["lists"] = { "people" => "function(head, row, req, row_info) { var response = {'code':200, 'headers':{}, 'body':'x'}; return response; }" }
|
169
|
+
@db.save_doc(design)
|
170
|
+
@db.list("foo", "people", "bar").should == "xxxx"
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "Attachments" do
|
176
|
+
|
177
|
+
it "should save and open inline attachments" do
|
178
|
+
@db.save_doc("_id" => "inline", "_attachments" => {
|
179
|
+
"foo.txt" => {
|
180
|
+
"content_type" => "text/plain",
|
181
|
+
"data" => "Hello World!"
|
182
|
+
}
|
183
|
+
})["ok"].should.be.true
|
184
|
+
|
185
|
+
@db.open_attachment("inline", "foo.txt").read.should == "Hello World!"
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should save and open standalone attachments" do
|
189
|
+
res = @db.save_doc("_id" => "attachable")
|
190
|
+
@db.save_attachment(res['id'], res['rev'],
|
191
|
+
"foo.txt", "Hello World!",
|
192
|
+
:content_type => "text/plain"
|
193
|
+
)["ok"].should.be.true
|
194
|
+
|
195
|
+
@db.open_attachment("attachable", "foo.txt").read.should == "Hello World!"
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should delete attachments" do
|
199
|
+
res = @db.save_doc("_id" => "deleteme", "_attachments" => {
|
200
|
+
"foo.txt" => {
|
201
|
+
"content_type" => "text/plain",
|
202
|
+
"data" => "Hello World!"
|
203
|
+
}
|
204
|
+
})
|
205
|
+
|
206
|
+
@db.delete_attachment("deleteme", res["rev"], "foo.txt")["ok"].should.be.true
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should rename attachments" do
|
210
|
+
res = @db.save_doc("_id" => "rename", "_attachments" => {
|
211
|
+
"foo.txt" => {
|
212
|
+
"content_type" => "text/plain",
|
213
|
+
"data" => "Hello World!"
|
214
|
+
}
|
215
|
+
})
|
216
|
+
|
217
|
+
@db.rename_attachment("rename", res["rev"], "foo.txt", "bar.txt")["ok"].should.be.true
|
218
|
+
@db.open_doc("rename")["_attachments"]["bar.txt"].should.not.be.nil
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|