Serving Images Stored in the Database with Rails

10:46 AM EDT Saturday, 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  | Tags Rails, Ruby, Caching

Comments Feed

1. I do feel it's worth pointing out the major downside of this approach: the entire image is loaded into a Ruby String anytime you are working with that model (by default). This can get to be a significant burden on a large traffic site with serving many images. I had a database teacher who always use to remind us, "File data belongs in a file system."

# Posted By James Edward Gray II on Thursday, May 15 2008 at 09:17 EDT

2. @James

That is true, this example was meant to mostly show how to download the image and use caching. For a real app, I would use attachment_fu and store the file data in a separate table, and attachment_fu supports that with the db_file option. The only time you even load that record would be in this controller, when the file gets cached.

One advantage of keeping the data in the DB is that there are no extra operations required to keep the file on the file system and record in the DB in sync. Also, you can just backup your database and you get the file data right along with it. Also if you have more than one app server, the file data is stored in a central location. But on a large app, storing a lot of file data in the DB could make the DB harder to manage, so storing the file data on a shared file system would make more sense. But for small to medium sites, as long as you keep the file data in a separate table from the record that has the data about the file, I prefer to keep everything the DB, YMMV.

# Posted By Paul Barry on Thursday, May 15 2008 at 10:25 EDT

3. Storing files in a database is not really a problem, since most database servers have great support for it.

I use this approach to store small thumbnails in my database. By using a has_many relation I can easily access my data without loading all the Photo data. I only load it when I need it. In combination with the caching feature, this is a very acceptable solution.

# Posted By Ariejan de Vroom on Thursday, September 11 2008 at 08:38 EDT

4. great example! right what I was looking for... now I'm trying to resize the image before storing it into the db... any tip or tutorial you'd suggest? Google is not helping at all so far :P

thanks for the example again!

# Posted By david on Monday, January 26 2009 at 05:41 EST

Add a Comment