<?xml version='1.0' encoding='utf-8' ?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Social Memory Complex: rails3</title>
<link href="https://www.socialmemorycomplex.net/tags/rails3/feed.xml" rel="self" />
<link href="https://www.socialmemorycomplex.net/tags/rails3/" />
<updated>2026-05-24T21:17:06+00:00</updated>
<id>https://www.socialmemorycomplex.net/tags/rails3/</id>
<entry>
  <title>GridFS with Mongoid and CarrierWave on Rails 3</title>
  <link href="http://socialmemorycomplex.net/2010/06/02/gridfs-with-mongoid-and-carrierwave-on-rails-3/" />
  <updated>2010-06-02T00:00:00+00:00</updated>
  <id>http://socialmemorycomplex.net/2010/06/02/gridfs-with-mongoid-and-carrierwave-on-rails-3/</id>
  <author><name>Jeremy Weiland</name></author>
  <content type="html"><![CDATA[<p>Over the last week I’ve started a project with <a href="https://guides.rails.info/3_0_release_notes.html">Rails 3</a> and I’m impressed. The increased configurability of the framework has not diminished its ease of use nor its core concepts in the slightest. You’ll have to get used to a few new conventions, especially regarding routing, but there’s lots of help out there.</p>

<p>Since this project is something I’m doing in my off time, I decided to experiment with <a href="https://www.mongodb.org/">MongoDB</a> using the <a href="https://mongoid.org">Mongoid</a> framework. I had played with MongoMapper before, but always felt like I was using an ActiveRecord clone that didn’t take advantage of the full capabilities of a document database and was forcing and ActiveRecord-style approach on me. With Mongoid you get has_many, has_one, and belongs_to relationships that map to MongoDB concepts like embedded documents. Mongoid is fully compatible with the <a href="https://github.com/rails/rails/tree/master/activemodel">ActiveModel</a> interface for Rails3, and things like associations and nested attributes work out of the box.</p>

<p>I also had been hearing great things about <a href="https://github.com/jnicklas/carrierwave">CarrierWave</a> from co-workers. It employs the concept of an “uploader” outside of the MVC ecosystem. The uploader handles resizing, storage, and all other details. In your model, you simply “mount” the uploader and you’re golden. Of course, for this project the killer feature is the GridFS storage option, which is something I wanted to play with.</p>

<p><a href="https://www.mongodb.org/display/DOCS/GridFS+Specification">GridFS</a> is a feature of MongoDB that allows storage of large files inside the database. It chunks the file into pieces and assigns a database index so the file can be associated with other documents. While CarrierWave takes care of getting the file <em>into</em> GridFS, serving the file back out is a bit trickier. CarrierWave gives you all you need to configure the url at which your file will be served, but you have to roll your own mechanism for serving it. But I’ll walk you through it.</p>

<p>I’ll assume you have the right version of Rails 3 installed - it’s changing too frequently to document here. Once you’ve got that, create your app with <code class="language-plaintext highlighter-rouge">rails appname --skip-activerecord</code> and navigate up in that piece.</p>

<p>I’ve discovered through trial and error that your Bundler config for all the gems mentioned here should track their respective master git branches, since a lot of the Rails 3 compatibility issues are still being worked out. I also track Rails edge because, at this point, why wouldn’t you? So, get your Gemfile looking like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>source :rubygems
gem 'bson_ext'

gem 'rails', :git =&gt; 'https://github.com/rails/rails.git', :branch =&gt; 'master'
gem 'mongoid', :git =&gt; 'git://github.com/durran/mongoid.git'
gem 'carrierwave', :git =&gt; "git://github.com/jnicklas/carrierwave.git"
gem 'mini_magick', :git =&gt; 'git://github.com/probablycorey/mini_magick.git'
</code></pre></div></div>

<p>I included <a href="https://github.com/probablycorey/mini_magick">MiniMagick</a>, but you can choose your own image processing library - just remember to change lines referencing MiniMagick in future code examples.</p>

<p>Now let’s install your gem bundle. I recommend calling <code class="language-plaintext highlighter-rouge">bundle install vendor</code> to get all your gems vendored in your Rails app, which makes tracking gems much more straightforward.</p>

<p>Next step is to run <code class="language-plaintext highlighter-rouge">rails generate mongoid:config</code> which generates a <code class="language-plaintext highlighter-rouge">config/mongoid.yml</code> file. You’ll need to fill in the details, but I recommend something a bit like this just to get started:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>defaults: &amp;defaults
  host: localhost

development:
  &lt;&lt;: *defaults
  database: appname_development

test:
  &lt;&lt;: *defaults
  database: appname_test
</code></pre></div></div>

<p>The generator will also place <code class="language-plaintext highlighter-rouge">require 'mongoid/railtie'</code> at the top of your <code class="language-plaintext highlighter-rouge">config/application.rb</code> file. I recommend setting up your generators with Mongoid by adding this line in the config block:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>config.generators do |g|
  g.orm :mongoid
  g.template_engine :erb # this could be :haml or whatever
  g.test_framework :test_unit, :fixture =&gt; false # this could be :rpsec or whatever
end
</code></pre></div></div>

<p>Those generator settings will allow the resource generator to give you exactly the models, views, and controllers you want by invoking <code class="language-plaintext highlighter-rouge">rails generate scaffold thing</code>. Now let’s go into <code class="language-plaintext highlighter-rouge">app/models/thing.rb</code> and add the following:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require 'carrierwave/orm/mongoid'

class Thing
  include Mongoid::Document
  mount_uploader :image, ImageUploader
end
</code></pre></div></div>

<p>This is the equivalent of Paperclip’s <code class="language-plaintext highlighter-rouge">has_attached_file</code> or Attachment_fu’s <code class="language-plaintext highlighter-rouge">has_attachment</code> - except that you don’t do all the configuration for upload processing there. Instead you do it in the uploader class, which you can generate by invoking <code class="language-plaintext highlighter-rouge">rails generate uploader image</code>. This will create an <code class="language-plaintext highlighter-rouge">uploaders/image.rb</code> file, which is a slight quirk because Rails doesn’t know how to find this file when looking up the <code class="language-plaintext highlighter-rouge">ImageUploader</code> class. We’re going to change the name of the file to <code class="language-plaintext highlighter-rouge">uploaders/image_uploader.rb</code> so it conforms to ruby’s conventions for class definition files.</p>

<p>I’m going to use MiniMagick to process thumbnails, so here’s my fully configured uploader file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require 'carrierwave/processing/mini_magick'

class ImageUploader &lt; CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  
  version :thumb do
    process :resize_to_fill =&gt; [80,80]
  end
end
</code></pre></div></div>

<p>There’s some CarrierWave settings we should set up globally, such as all the MongoDB and GridFS stuff. An initializer is the best place for that junk, so stick this in a new file called <code class="language-plaintext highlighter-rouge">config/initializers/carrierwave.rb</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CarrierWave.configure do |config|
  config.grid_fs_database = Mongoid.database.name
  config.grid_fs_host = Mongoid.config.master.connection.host
  config.storage = :grid_fs
  config.grid_fs_access_url = "/images"
end
</code></pre></div></div>

<p>Note the <code class="language-plaintext highlighter-rouge">config.grid_fs_access_url = "/images"</code> line, which helps CarrierWave figure out what url to serve this under. The actual url it generates will look like <code class="language-plaintext highlighter-rouge">/images/uploads/version_filename.jpg</code>, which is fine for now - you can configure this to your liking later.</p>

<p>Now we just need to update the form to allowing file uploads. Make your “thing” form partial look like this, noting the file field and mulitpart form lines in particular:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%= form_for(@thing, :html =&gt; { :multipart =&gt; true }) do |f| %&gt;
  &lt;% if @thing.errors.any? %&gt;
    &lt;div id="error_explanation"&gt;
      &lt;h2&gt;&lt;%= pluralize(@thing.errors.count, "error") %&gt; prohibited this thing from being saved:&lt;/h2&gt;

      &lt;ul&gt;
      &lt;% @thing.errors.full_messages.each do |msg| %&gt;
        &lt;li&gt;&lt;%= msg %&gt;&lt;/li&gt;
      &lt;% end %&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;% end %&gt;
  
  &lt;%= f.label :image %&gt;
  &lt;%= f.file_field :image %&gt;

  &lt;div class="actions"&gt;
    &lt;%= f.submit %&gt;
  &lt;/div&gt;
&lt;% end %&gt;
</code></pre></div></div>

<p>And let’s amend the show view to display the image. Notice we pass the version into the <code class="language-plaintext highlighter-rouge">url</code> method to access a particular version.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;p id="notice"&gt;&lt;%= notice %&gt;&lt;/p&gt;
&lt;%= image_tag @thing.image.url(:thumb) %&gt;

&lt;%= link_to 'Edit', edit_thing_path(@thing) %&gt; |
&lt;%= link_to 'Back', things_path %&gt;
</code></pre></div></div>

<p>We can start up the server by invoking <code class="language-plaintext highlighter-rouge">rails server</code>. Navigating to <code class="language-plaintext highlighter-rouge">https://localhost:3000/things/new</code> should give us a form where we can select an image to upload. The image should get uploaded and the “thing” saved without a hitch, but when it redirects you to view the “thing” there will be a broken image waiting for you. This is because Rails has no freakin’ clue how to access the file via GridFS. So we need to tell it how.</p>

<p>In order to serve the image as quickly as possible, we need a way to access GridFS without involving the entire Rails slow-ass stack. Enter Rails Metal, which allows you to process requests directly from Rack. While Rails 2 required you to place metal processing in its own directory under app, Rails 3 bakes Rack support directly into the inheritance hierarchy of ActionController, allowing you to do something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require 'mongo'

class GridfsController &lt; ActionController::Metal
  def serve
    gridfs_path = env["PATH_INFO"].gsub("/images/", "")
    begin
      gridfs_file = Mongo::GridFileSystem.new(Mongoid.database).open(gridfs_path, 'r')
      self.response_body = gridfs_file.read
      self.content_type = gridfs_file.content_type
    rescue
      self.status = :file_not_found
      self.content_type = 'text/plain'
      self.response_body = ''
    end
  end
end
</code></pre></div></div>

<p>Save this file as <code class="language-plaintext highlighter-rouge">app/controllers/gridfs_controller</code>. Notice that we’re pulling details about the request path directly out of the request. By default, CarrierWave stores files in GridFS under “uploads/filename”. Therefore, we need to turn the request path (<code class="language-plaintext highlighter-rouge">/images/uploads/filename</code>) into a GridFS file path by simply removing the “/images/” (note both slashes). All of these settings are fully configurable in CarrierWave, but that’s beyond the scope of this article - just don’t forget to modify this controller if you change the url or GridFS storage path.</p>

<p>Now the last part is to set up the route for the image. Open up <code class="language-plaintext highlighter-rouge">config/routes.rb</code> and add a line for our GridfsController:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Example::Application.routes.draw do |map|
  match "/images/uploads/*path" =&gt; "gridfs#serve"
  resources :things
end
</code></pre></div></div>

<p>This maps a url like <code class="language-plaintext highlighter-rouge">/images/uploads/thumb_image.jpg</code> to the GridfsController’s <code class="language-plaintext highlighter-rouge">serve</code> action.</p>

<p>You should now see an thumbnail image on the show view for the “thing”. Congratulations - you’re cooking with GridFS now! There’s a ton of other cool stuff in Rails 3, CarrierWave, and Mongoid, but this should give you the basics for how to handle uploads with GridFS. Have fun, and let me know if I fucked anything up!</p>
]]></content>
</entry>
</feed>
