Setting up carrierwave file uploads using GridFS on Rails 3 and mongoid
Nov 07
Why the hassle?
I recently needed to set up a prototype on Heroku using MongoDB. Since they offer an Add-On for configuring your app with MongoHQ’s database, I immediately went for that and decided to use Mongoid. However, once you want to use file uploads with e.g. carrierwave, you realize that Heroku’s file system is read-only. You can go for Amazon S3 storage, but that’ll cost you extra. But wait – if you are using MongoDB, why not settle for GridFS? I found a tutorial written by Matsimitsu on @jkreeftmeijer’s blog, but employing MongoMapper and Rails 2.3.x made it obsolete and incompatible with my needs. After digging into the topic for some time I finally got it working. Hence this guide.
Setting up and configuration
I won’t go into details of setting up either a Heroku app or a MongoHQ database, everything is available on their websites. After you set up a Rails 3 app, be sure to include the following in your .Gemfile:
#.Gemfile
gem 'carrierwave'
gem 'mongoid', '2.0.0.beta.19'Configuring the database connection
To use the MongoHQ database instead of Heroku’s default PostgreSQL instance, you need to configure the production config/mongoid.yml using MongoHQ connection details:
#config/mongoid.yml
production:
host: example.mongohq.com
port: 27063
username: mongo
password: password
database: databaseFile uploads
I chose carrierwave over Paperclip, since Paperclip is not that easy to get working with Mongoid, as e.g. @meskyanichi found out.
In order to get carrierwave properly working using the remote GridFS storage, you need the following configuration in it’s initializer.
#initializers/carrierwave.rb
CarrierWave.configure do |config|
config.storage = :grid_fs
# Same as your MongoHQ database conenction parameters
config.grid_fs_connection = Mongoid.database
# Storage access url
config.grid_fs_access_url = "/grid"
endUpdate: Shorter config thanks to (shorter thanks to @joshkalderimis)
Your model will probably look something like this:
#app/models/player.rb
class Player
include Mongoid::Document
mount_uploader :avatar, AvatarUploader
field :name
endAnd the corresponding uploader:
#app/uploaders/avatar_uploader.rb
class AvatarUploader < CarrierWave::Uploader::Base
storage :grid_fs
def store_dir
"#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
endNow carrierwave will store images properly, but for your Rails app to serve images from the Mongo GridFS storage using the carrierwave image url methods, you need to process those requests separately. @Matsimitsu used Rails Metal for this purpose, but since Metal has been removed from Rails 3, you need to use separate Rack middleware. Here’s the Rack code for serving images from GridFS when routed to the /grid (same as in the carrierwave initializer) path:
#lib/serve_gridfs_image.rb
class ServeGridfsImage
def initialize(app)
@app = app
end
def call(env)
if env["PATH_INFO"] =~ /^\/grid\/(.+)$/
process_request(env, $1)
else
@app.call(env)
end
end
private
def process_request(env, key)
begin
Mongo::GridFileSystem.new(Mongoid.database).open(key, 'r') do |file|
[200, { 'Content-Type' => file.content_type }, [file.read]]
end
rescue
[404, { 'Content-Type' => 'text/plain' }, ['File not found.']]
end
end
endDon’t forget to enable this middleware in your Rails app application.rb config file, by adding it to the config.middleware parameter:
#config/application.rb
config.middleware.use "ServeGridfsImage"Boom!
This config should be enough for both uploading and serving file uploads for your app using separate GridFS storage e.g. on Heroku. I hope you’ll find it useful. If you have any questions or suggestions on how to make it simpler, be sure to put them down in the comments!

