1 # Ruby layer for accessing CouchDB
3 # Copyright (c) Stuart Glaser <StuGlaser@gmail.com>
8 # The JSON parser stupidly does not accept just strings.
9 def hacked_json_parse(json)
10 boxed = "[ " + json + " ]"
11 boxed_parsed = JSON.parse boxed
15 class CouchException < StandardError
16 attr_reader :couch, :response
18 def initialize(couch, response)
24 #json = JSON.parse @response.body
25 json = hacked_json_parse @response.body
26 json['error']['id'] + "\n" + json['error']['reason']
30 "#{@response.class}\n" +
31 "Couch Backtrace:" + couch_backtrace +
39 attr_accessor :host, :port
41 def initialize(host, port)
47 request Net::HTTP::Get.new(path)
50 def put(path, data) # TODO: support text/javascript
51 req = Net::HTTP::Put.new(path)
52 req['content-type'] = 'text/javascript'
53 req['content-type'] = 'application/json'
58 def post(path, data) # TODO: support text/javascript
59 req = Net::HTTP::Post.new(path)
65 request Net::HTTP::Delete.new(path)
69 resp = Net::HTTP.start(@host, @port) do |http|
72 if not resp.kind_of?(Net::HTTPSuccess)
73 raise CouchException.new(self, resp)
85 def initialize(server, name)
91 resp = @server.get doc_path(doc_id)
93 hacked_json_parse resp.body
97 resp = $server.put doc_path(doc_id), body.to_json
99 hacked_json_parse resp.body
103 resp = $server.post doc_path(''), body.to_json
104 #JSON.parse resp.body
105 hacked_json_parse resp.body
109 resp = $server.delete doc_path(doc_id)
110 #JSON.parse resp.body
111 hacked_json_parse resp.body
115 path = doc_path('_temp_view')
116 req = Net::HTTP::Post.new(path)
117 req['content-type'] = 'text/javascript'
122 def temp_view(body) # TODO: content-type
123 resp = $server.post doc_path('_temp_view'), body
124 #JSON.parse resp.body
125 hacked_json_parse resp.body
129 '/' + @name + '/' + doc_id
134 # Represents a document in a Couch database
139 attr_accessor :_attachments
142 @_attachments = [] if @_attachments.nil?
147 attr_reader :field_names
151 @field_names = [] if @field_names.nil?
160 doc.from_couch! $db.get(id)
162 rescue CouchException=>ex
163 return nil if ex.response.instance_of? Net::HTTPNotFound
169 def Doc.find_or_create(db, id)
171 return doc unless doc.nil?
188 results = if @id.nil? # POST
193 @_rev = results['rev']
199 raise "Never saved" if @id.nil?
200 self.from_couch! @db.get(@id)
205 results = @db.delete(@id + "?rev=#{@_rev}")
211 a = Attachment.new self
216 # Converts the object to (unparsed) JSON format for couch
218 # Fills in the fields for saving to the db
220 fields['_rev'] = @_rev unless @_rev.nil?
221 self.class.field_names.each do |field|
222 fields[field] = instance_variable_get '@' + field.to_s
224 unless _attachments.empty?
225 # Let ye of functional natures rejoice!
226 fields['_attachments'] = _attachments.inject({}) do |all, a|
233 # Takes (parsed) JSON from couch and places it into the fields
234 def from_couch!(couch)
236 @_rev = couch['_rev']
237 self.class.field_names.each do |field|
238 instance_variable_set('@' + field.to_s, couch[field.to_s])
240 unless couch['_attachments'].nil?
242 couch['_attachments'].each do |name,value|
243 a = Attachment.new self
244 a.from_couch! name, value
255 attr_accessor :name, :type, :data
256 attr_reader :length, :stub
267 path = @doc.id + '?attachment=' + @name
278 a = { 'type' => type, 'stub' => stub }
279 a['data'] = data unless stub
280 return { 'asdf' => { 'data' => 123123 } } if stub
284 def from_couch!(name, couch)
286 @stub = couch['stub'] or false
287 @type = couch['type']
288 @length = couch['length']
297 # TODO: delete this test code
298 $server = Couch::Server.new 'localhost', 8888
299 $db = Couch::Db.new $server, 'teddybear'
310 a.data = "something suspicious"
316 # class Foo < Couch::Doc