December 23, 2009

Posted by John

Tagged mongodb, mongomapper, and plugin

Older: Twizzle Your Deplizzles

Newer: The Balance of Convention and Innovation

Getting A Grip on GridFS

Update: This project is renamed and now actively developed as joint.

I know it is almost Christmas and your minds are beginning to turn to gifts and family, but hang with me for a few more minutes. My buddy Jon pointed me to Grip yesterday. I liked the idea but not all of the implementation. I’ve been doing some GridFS stuff lately so I decided to take some time and tweak it to be more what I need.

You can get my fork of Grip on Github. Below is a simple example of how it works (you could also check out the tests).

class Foo
  include MongoMapper::Document
  include Grip
  
  has_grid_attachment :image
  has_grid_attachment :pdf
end

image = File.open('foo.png', 'r')
pdf = File.open('foo.pdf', 'r')
foo = Foo.create(:image => image, :pdf => pdf)

foo.image # contents read from gridfs for serving from rack/metal/controller
foo.image_name # foo.jpg
foo.image_content_type # image/png
foo.image_size # File.size of the file
foo.image_path # path in grid fs foo/image/:id where :id is foo.id

The main changes I made were to store name, size and content_type along with the path to the file. I also made it so those are assigned when the file is set so that they can be used in validations. Some other ideas I have for the plugin are adding image resizing with MojoMagick and common validations (much like most of the file upload plugins out there).

What I really like about Grip is its simplicity. A single, small file, an include, a call to has_grid_attachment, and you can start storing files. The great thing about working on this is it gave me some wonderful ideas for how to standardize the process of creating and declaring MongoMapper plugins.

I’ll be adding those to ideas to MongoMapper over the next few weeks as I get time and I think people are going to be stoked about what I’ve come up with (thanks to Brandon for brainstorming the plugin API with me).

Behind the Scenes

As I said, behind the scenes, Grip uses GridFS (more on GridFS), Mongo’s specification for storing files in Mongo.

The good news is that the API for storing files in GridFS using Ruby is nearly identical to using Ruby’s File class. Unfortunately, that is also the bad news, in my opinion, as I find Ruby’s File open, read and close a bit awkward. Here are a few examples pulled almost directly from Grip:

# write foo.jpg to grid fs
file = File.read('foo.jpg')
GridFS::GridStore.open(database, 'foo.jpg', 'w', :content_type => 'image/jpg') do |f|
  f.write(file)
end

# read foo.jpg from grid fs
GridFS::GridStore.read(database, 'foo.jpg')

# delete foo.jpg from grid fs
GridFS::GridStore.unlink(database, 'foo.jpg')

Not horrible but not beautiful. There is a bunch of GridFS related code on Github. To highlight a few interesting ones:

That is all for now. Enjoy the GridFS goodness and the future hope of an awesome MongoMapper plugin interface.

14 Comments

  1. Would it make more sense to use a nested object instead of underscore notation? Such as, in your example, foo.image would return a nested document with name, content_type, size, and path keys? If we have the ability to nest objects, it seems like we should make the most of it!

  2. @Michael – Yeah, that makes sense, though the underscores don’t bother me much in this instance.

  3. Mihael Konjević Mihael Konjević

    Dec 24, 2009

    I’ve created RackDAV driver for GridFS which allows you to access GridFS files through WebDAV interface. Here is the link if anyoune is interested http://github.com/retro/gridfs-rackdav

  4. I’ve been using http://github.com/jnicklas/carrierwave/ which is pretty nice so far (combined with a customized version of the gridfs-metal http://gist.github.com/264077)

  5. Hi, very cool, I saved an image into Mongo Database but I don’t know how to show that image in the view in rails.

    Thanks

  6. @joselo – you’ll want to take a look at something like Rails Metal for serving from GridFS – for showing the image in a view

  7. @Joselo Look into Rails send_data too as a starting point.

  8. @Matt – The next time I will read, exelent It Works!!

    Thanks

  9. @John Ok

    Thanks

  10. Robby Colvin Robby Colvin

    Jan 15, 2010

    Is this faster than serving files directly through nginx? Does going through MongoMapper and GridFS add extra overhead?

  11. @Robby Colvin: Yes, I’m sure using ruby instead of nginx will add some overhead. I’m sure for most people though it will be fast enough.

  12. When trying to use this on an Embedded Document model, I get the following error:
    grip.rb:30:in `has_grid_attachment’:NoMethodError: undefined method `after_save’ for Field:Class

    Does it only work for base level Documents?

  13. Karl Baum Karl Baum

    Jan 22, 2010

    We’re looking to attach email files from in memory to our mongo mapper document. The problem is that since they are not files, we need to write them to temporary files to allow grip to read from them. Is there a better way to make this work without having to write to a temporary file?

    thx

  14. neither image_content_type nor image_path are in the current codebase of grip. this should be changed/updated in this poting! furthermore the method image_id exists, which returns the gridfs-object-id to correctly serve the file to the client. john, can you update your posting?
    regards, m

Sorry, comments are closed for this article to ease the burden of pruning spam.

About

Authored by John Nunemaker (Noo-neh-maker), a programmer who has fallen deeply in love with Ruby. Learn More.

Projects

Flipper
Release your software more often with fewer problems.
Flip your features.