---
title: On-the-fly image handling with Dragonfly
teaser: 'Dragonfly lets you dynamically crop, resize and transform images through
  the URL.

  '
tags: rails,ruby,new bamboo,web
author: Mark Evans
published_on: 2010-01-13
---

_This post was originally published on the New Bamboo blog, before [New Bamboo
joined thoughtbot in London][new-bamboo-thoughtbot]._

---

There are some fantastic gems and plugins out there for handling images in Rails
apps, such as Paperclip, Attachment-fu, etc., that on the whole are well-written
and do a pretty good job.

However, something has always narked me when using them.

In general, to add a `preview_image` to, say, your `Album` model, you do
something like this:

```ruby
class Album
  some_macro_method :preview_image,
    :thumbnails => {
      :listing => "300x200",
      :thumb => "100x100!",
      :small => "50x50#"
    }
end
```

Then in the view that shows the image, you can use something like this:

```erb
<%= album.preview_image.url(:listing) %>
...
<%= album.preview_image.url(:thumb) %>
...etc.
```

This is all very well, provided you know the exact sizes of the thumbnails that
you will need at the very beginning.

Unfortunately, more often than not, this is not the case, and we find ourselves
running some 'reprocess' task every time a new thumbnail size is added, or one
is changed.

Furthermore, we might not always want to use all of those thumbnails. If we use
these models in a script which makes no use of the images, or in our tests, then
having to process thumbnails every time we create a model is expensive and
unnecessary!

Taking these things into account, it seems clear to me that the fundamental
problem is that the thumbnail specifications are **in the wrong place!**

Images, and what size they should be, are *visual* concerns, so surely the
correct place for specifying thumbnails is in the **view**.

## The Solution

What we really want is to have our model looking like this:

```ruby
class Album
  some_macro_method :preview_image
end
```

and our view looking like this:

```erb
<%= album.preview_image.url("300x200") %>
...
<%= album.preview_image.url("100x100!") %>
...etc.
```

The way to achieve this will be to generate thumbnails *on-the-fly* - this will
both remove the need to reprocess thumbnails when there are changes, and
minimize the creation of unused thumbnails.

NB If you're concerned about performance at this point, this is easy to optimise
using HTTP caching - we'll mention that later - what's important for now is that
the thumbnails are specified in the **correct place**.

## Dragonfly

Although I'd been thinking about this problem for a long time, it's taken a
while to get round to actually creating a ruby gem for doing it!

So at last, I'm very pleased to introduce Dragonfly, a Rack-based ruby gem for
processing/encoding on the fly.

There are already one or two gems which deal with on-the-fly processing (see
below), and while these are good for their specific tasks, I wanted one which
was generic enough as to allow the use of different processing libraries,
different data stores, etc.

From the start it has been designed to be as modular and extendable as possible,
making it highly configurable, but falling back to sensible defaults for those
who just want to do easy thumbnailing without delving into configuration
details.

In particular, it was designed with the following in mind:

  - useable outside of Rails as a Rack application
  - useable without ActiveRecord
  - useable with any media type (not just images)
  - useable with any kind of data store (filesystem, S3, SQL, CouchDB, etc.)
  - ability to create custom processors (more than just resize, rotate, etc.)
  - ability to create custom encoders (i.e. any format - png, jpg, svg, txt,
    etc.)
  - should make good use of HTTP caching for performance
  - ability to have multiple dragonfly apps in the same process, each with their
    own separate configuration

Given the above points regarding not being tied to Rails/ActiveRecord, it's been
designed primarily as a Rack application, but with an optional extension module
for using with ActiveRecord.

Using with Rails is simply a matter of using it as a Rails Middleware, and
configuring appropriately (which is done for you if you use the supplied
generator or helper Rails configuration file).

## Basic usage as a Rack application

Basic usage of a dragonfly app involves storing data (e.g. images),
then serving that data, either in its original form, processed, encoded or both.

We set up the dragonfly app and run it (e.g. in our rackup file `config.ru`):

```ruby
require 'rubygems'
require 'dragonfly'

app = Dragonfly::App[:my_app_name]

app.configure_with(Dragonfly::RMagickConfiguration)
app.configure do |c|
  # any other configuration here
end

run app
```

(If you are unfamiliar with how to run Rack apps with config.ru, then see this
[rackup tutorial].

Elsewhere in our code, we store some content:

```ruby
# Store
uid = app.store(File.new('path/to/image.png'))
# ===> returns a unique uid for that image, "2009/11/29/145804_file"
```

We can get the url for a processed and encoded version of this content:

```ruby
url = app.url_for(uid, '30x30', :gif)
# ===> "/2009/11/29/145804_file.gif?m=resize&o[geometry]=30x30&s=aa78e877113f6bc9"
```

Now when we visit the url
`/2009/11/29/145804_file.gif?m=resize&o[geometry]=30x30&s=aa78e877113f6bc9` in
the browser (given the app is running of course), we get the resized image!

NOTE: the GET parameter `s=aa78e877113f6bc9` is for protecting from <acronym
title="Denial Of Service">DOS</acronym> attacks. You can turn this off if you
wish - see the [dragonfly documentation].

## Using in Rails

As mentioned earlier, the ActiveRecord/Rails part of the code is a layer which
sits on top of the main app.

In fact, the main task of the ActiveRecord extension is *storing the content uid
mentioned above*.

Let's say we want to add a 'cover image' attribute to our Album model.

Then in environment.rb:

```ruby
config.gem 'rmagick',    :lib => 'RMagick'
config.gem 'rack-cache', :lib => 'rack/cache'
config.gem 'dragonfly',  :lib => 'dragonfly/rails/images', :source => 'http://gemcutter.org'
```

The file 'dragonfly/rails/images' creates a Dragonfly App and configures it for
use with Rails, then inserts it into the Rails middleware stack (see the [rails
integration docs] for more info). Note that we're using [RMagick] for image
processing/encoding, and [Rack::Cache] to cache requests.

Migration:

```ruby
add_column :albums, :cover_image_uid, :string
```

Model:

```ruby
class Album < ActiveRecord::Base
  image_accessor :cover_image            # Defines reader/writer for cover_image
end
```

View (for uploading via a file field):

```erb
<% form_for @album, :html => {:multipart => true} do |f| %>
  ...
  <%= f.file_field :cover_image %>
  ...
<% end %>
```

View (to display):

```erb
<%= image_tag @album.cover_image.url(:gif) %>
<%= image_tag @album.cover_image.url('400x200') %>
<%= image_tag @album.cover_image.url('100x100!', :png) %>
<%= image_tag @album.cover_image.url('100x100#') %>
<%= image_tag @album.cover_image.url('50x50+30+30sw', :tiff) %>
<%= image_tag @album.cover_image.url(:rotate, 15) %>
...etc.
```

As you can see from the migration, the model's main concern is storing the uid
for the content. The method `album.cover_image.url(...)` is a wrapper around the
Dragonfly app's `url_for(uid, ...)` method mentioned earlier, using the stored
cover image uid.

## Extra niceties

The ActiveRecord extension module actually has a number of useful helper
methods, which should make it extremely easy and intuitive to use, as if the
image is like any other model attribute.

See the [docs for using with ActiveRecord][dragonfly-ar] for more details, but
for now, here are some examples of some of the things you can do.

Assignment:

```ruby
album = Album.new

album.cover_image = "\377???JFIF\000\..."             # can assign as a string...
album.cover_image = File.new('path/to/my_image.png')  # ... or as a file...
album.cover_image = some_tempfile                     # ... or as a tempfile

album.cover_image          # => #<Dragonfly::ActiveRecordExtensions::Attachment:0x103ef6128...

album.cover_image = nil
album.cover_image          # => nil
```

Inspection and processing:

```ruby
album.cover_image.width                          # => 280
album.cover_image.mime_type                      # => 'image/png'

album.cover_image.transform!('30x30#nw', :gif)   # (operates on self)

album.cover_image.width                          # => 30
album.cover_image.mime_type                      # => 'image/gif'
```

Write to a file:

```ruby
album.cover_image.to_file('out.png')
```

URLs:

```ruby
album.cover_image.url(:png)
# => '/media/2009/12/05/170406_file.png'
album.cover_image.url('300x200#nw', :gif)
# => '/media/2009/12/05/170406_file.gif?m=resize_and_crop&o[height]=...'
```

Generating (for tests/populate tasks):

```ruby
album.cover_image = Dragonfly::App[:images].generate(300, 200)
```

(the above line of code assumes the Dragonfly app is called `:images`).

There are a number of validations you can use too:

```ruby
class Album
  validates_presence_of  :cover_image
  validates_size_of      :cover_image, :maximum => 500.kilobytes
  validates_mime_type_of :cover_image, :in => %w(image/jpeg image/png image/gif)
  validates_property :width, :of => :cover_image, :in => (0..400)

  image_accessor :cover_image
end
```

## Per-blog-post thumbnails

Now that we're generating thumbnails on the fly, we can wave 'bye-bye' to
reprocess tasks every time we change the sizes of our thumbnails, and smugly
shout 'so long suckers' to tests bogged down by unnecessary image resizing.

However, it becomes apparent that now that our thumbnail definitions are in the
correct place, other things become easier.

One example is **this blog**!!

We have a number of users who want to create blog posts with images sized on a
per-blog-post basis.

Previously this would have been difficult, because we'd have had to specify the
sizes up front, e.g.

```ruby
class BlogPost
  some_macro_method :image,
    :thumbnails => {
      :small => "100x100!",
      # ...etc.
    }
end
```

Alternatively the user could resize the image themselves before uploading, and
then use the original, but this is tedious.

Now we are generating thumbnails on the fly, this is trivial. There are a number
of ways we could achieve this, but this blog uses something along the lines of
the following (actually it has multiple images, but for simplicity, let's assume
there is one image per blog post):

First, we add an image attribute to the blog post model.

Migration:

```ruby
add_column :blog_posts, :image_uid, :string
```

Model:

```ruby
class BlogPost < ActiveRecord::Base
  image_accessor :image
end
```

Then we allow the user to add tags like `img[300x200!]` to the blog post.

Before rendering, substitute all instances of e.g. `img[300x200!]` with
`<img src="#{blog_post.image.url('300x200!')}" />`

Here are some examples for this post!!

<table style="width:100%;">
  <tr><td>150x150</td><td>![](https://images.thoughtbot.com/new-bamboo/blog/dragonfly/E4TckVgRhuDfLSiKsraJ_image_1.jpg)</td></tr>
  <tr><td>150x150!</td><td>![](https://images.thoughtbot.com/new-bamboo/blog/dragonfly/OD9L5XlDRGSMi361B6Bk_image_1.jpg)</td></tr>
  <tr><td>150x150#</td><td>![](https://images.thoughtbot.com/new-bamboo/blog/dragonfly/wWjIx44REu90YmZ8t5T6_image_1.jpg)</td></tr>
  <tr><td>100x100+250+150nw</td><td>![](https://images.thoughtbot.com/new-bamboo/blog/dragonfly/5dtXipz3QQmnMltJqD6x_image_1.jpg)</td></tr>
</table>

(image taken from this [flickr album])

## Flexible Configuration

Dragonfly can be configured in various ways to suit various use cases. View the
docs to see some [example use cases], such as using with Heroku and S3, and text
image replacement.

## Other approaches

Other on-the-fly processing libraries of note include [Fleximage] and [Rack::Thumb]
(sorry if I've overlooked any other obvious ones!).

These are very good at what they do, but don't quite encompass all the aspects I
listed above (among other things, Fleximage is tied to Rails and ActiveRecord,
while Rack::Thumb is a lightweight Rack middleware that deals only with image
resizing/cropping), which is why I felt the need to build Dragonfly.

## Getting it

The gem is hosted on [gemcutter], installed in the usual way.

```sh
sudo gem install dragonfly
```

The [dragonfly code] and [dragonfly docs] are both available on GitHub.

Feel free to fork away, contribute, suggest improvements etc.

Enjoy!

[rackup tutorial]: http://wiki.github.com/rack/rack/tutorial-rackup-howto
[dragonfly documentation]: http://markevans.github.com/dragonfly/file.GettingStarted.html
[rails integration docs]: http://markevans.github.com/dragonfly/file.UsingWithRails.html
[Rack::Cache]: http://tomayko.com/src/rack-cache/
[RMagick]: http://rmagick.rubyforge.org/
[dragonfly-ar]: http://markevans.github.com/dragonfly/file.ActiveRecord.html
[flickr album]: http://www.flickr.com/photos/gregbm/262791325
[example use cases]: http://markevans.github.com/dragonfly/file.ExampleUseCases.html
[Fleximage]: http://github.com/Squeegy/fleximage
[Rack::Thumb]: http://github.com/akdubya/rack-thumb
[gemcutter]: http://gemcutter.org/gems/dragonfly
[new-bamboo-thoughtbot]: https://thoughtbot.com/blog/new-bamboo-joins-thoughtbot-in-london
[dragonfly code]: http://github.com/markevans/dragonfly
[dragonfly docs]: http://markevans.github.com/dragonfly/
