Serving Images Stored in the Database with Rails

April 19, 2008

Some Rails applications accept images uploaded by users via the application, such as profile images. You have a few choices as to how to store the image. I'm not going to go over them or debate the merits of each in this article. Instead I'm going to cover how to handle it if you've decided that you want to store your images in the database.

So to get started with the example, create a rails app and an image model to hold the image data:

$ rails myapp
$ cd myapp
$ script/generate model image file_name:string content_type:string \
file_size:integer file_data:binary
$ rake db:migrate

Ok, now let's put the awesomest image ever into our database:

$ script/console 

>> require 'net/http'
=> []

>> host = 'farm1.static.flickr.com'
=> "farm1.static.flickr.com"

>> path = '/62/163977533_55fd5ddd9b_o_d.jpg'
=> "/62/163977533_55fd5ddd9b_o_d.jpg"

>> res = Net::HTTP.get_response(host, path)
=> #<Net::HTTPOK 200 OK readbody=true>

>> Image.create!(:file_name => "awesome.jpg", 
  :content_type => res['Content-type'], 
  :file_size => res.body.size, 
  :file_data => res.body)
=> #<Image id: 1, file_name: "awesome.jpg"...

I've added some spacing for dramatic effect. Now, create an images controller at app/controllers/images_controller.rb:

class ImagesController < ApplicationController
  caches_page :show
  def show
    if @image = Image.find_by_file_name(params[:file_name])
      send_data(
        @image.file_data, 
        :type => @image.content_type,
        :filename => @image.file_name,
        :disposition => 'inline'
      )
    else
      render :file => "#{RAILS_ROOT}/public/404.html", :status => 404
    end
  end
end

Then setup a route:

map.connect "/images/*file_name", :controller => "images", :action => "show"

Next make sure caching is turned on for your development environment. Edit this line in config/environments/development.rb:

config.action_controller.perform_caching = true

And finally, start up rails and view the image in your browser at http://localhost:3000/images/awesome.jpg. If you look in your log, you will see a line like this:

Cached page: /images/awesome.jpg (0.00100)

But if you refresh your browser or hit the same URL from a different browser, you will not see that anymore. That's because we've cached the file to local disk and it can be served directly from there by the web server without going through rails. Mongrel does this by default on your local development setup, but your production environment might require some configuration to make sure that happens. The guys at Rails envy have a great article that has the details on how to configure our web and application servers to do that. The article provides more detail on page caching as well, including how to clear the cache when images change, so you should definitely check that out.

Posted in Technology | Tags Rails, Ruby, Caching | 4 Comments