Tuesday, March 17, 2009

Complete Recipe Upload to CouchDB

‹prev | My Chain | next›

I got the JSON dump of recipes in pretty good shape last night simply by mucking with the various options that ActiveRecord's to_json method recognizes (:include, :except, and :methods). That accounts for most of a recipe's infomation, but not the photos.

So how to deal with photos? Continue to use a CouchDB-tailored attachment_fu (or switch to a CouchDB tailored paperclip)? Fortunately, the answer is a little more direct than that—CouchDB itself supports attachments.

My preference would be to use the nicer REST API that CouchDB provides for "Standalone Attachments" (see the CouchDB HTTP Document API, toward the bottom for more details). That is a recent addition, not available in the stock 0.8 that is packaged in Ubuntu's universe. Rather than shaving that yak, I will stick with "Inline" CouchDB attachments for now.

The thing about inline attachments in CouchDB is that they need to be Base64 encoded (and stripped of newline characters). So we need to grab the images from the file system (via attachment_fu that the old code uses), Base64 encode it, and put it into CouchDB. For CouchDB to recognize them, they need to be added in the record's _attachments key.

To grab it from the file system:
jpeg = File.open(recipe.image.full_filename).read
Base64 encoding (and removing newlines) is easy enough:
require 'base64'
Base64.encode64(jpeg).gsub(/\n/, '')
To get this into the JSON data structure (and thus into CouchDB), define an _attachements method in the Recipe class:
class Recipe
def _attachments
self.image.filename =>
:data => Base64.encode64(File.open(self.image.full_filename).read).gsub(/\n/, ''),
:content_type => "image/jpeg"
Next update the to_json method to include the _attachments method in the :methods option:
json = recipe.to_json(
:methods => [:tag_names, :_id, :_attachments],
:include => {
:preparations =>
:include =>
:ingredient => { :except => :id }
:except => [:ingredient_id, :recipe_id, :id]
:tools => { :except => [:id, :label] } },
:except => [:id, :label])
Lastly, put this into the DB with a simple RestClient put:
RestClient.put "http://localhost:5984/eee-meals/#{recipe._id}", json, 
:content_type => 'application/json'
Viewing the record in futon, we see that it does have a JPEG image attachment:

Clicking the attachment returns the original image:

At this point, we have a nice mechanism for transporting the data from the old relational DB into CouchDB. It needs to be expanded to run through all recipes (and maybe to add some error handling). Also, we still have to get this working for meal documents. The spike, and that is what this has been—a spike to understand how to transfer data from the legacy Rails application into CouchDB—is complete.

No comments:

Post a Comment