Paperdragon is another gem for image processing in Ruby. It has a very explicit DSL that gives you full control about what is going on when.

Now, please don’t stop reading. I know exactly what you’re thinking: “We got Paperclip, we got CarrierWave, and there’s also Dragonfly. Why the hell another gem?”.

Alternative To What?

First of all: Paperdragon uses Dragonfly under the hood. It’s a great gem. However, I couldn’t get the model layer to do what I want.

Then, Paperclip is extremely helpful. This gem is perfect if you don’t need any fine tuning. It allows you to quickly declare formats, cropping, etc. with a nice configuration DSL. What then happens when uploading is not your business.

CarrierWave almost does what I wanted. But still, I was missing the ability to take control myself because I’m a control freak.

Also, all the other gems hook into ActiveRecord’s callbacks. I think the concept of AR’s callbacks is one of the worst things I’ve seen with Rails. Not that those gems use callbacks in the wrong way – the way other people use callbacks makes me mad.

Take Control Of Your Images!

Ok control freaks, here we go. Let’s look at how Paperdragon does it.

class User < ActiveRecord::Base
  include Paperdragon::Model
 
  processable :image
  serialize :image_meta_data
end

Oh, paperdragon doesn’t know anything about ActiveRecord or whatever ORM you use. You could use paperdragon in a PORO and it wouldn’t know it.

By calling processable paperdragon will add a reader method #image to your model that allows accessing the processing API – nothing else happens!

A second requirement is to provide accessors for a field named image_meta_data. It uses this field to store, well, meta data like the images’ paths as a hash. I use serialize from ActiveRecord. And of course, I added a TEXT column with the same name in my users table.

We’ll see how these two play together in a minute.

Uploading

Now, what’s happening when you upload an image?

Processing and storing an uploaded image is an explicit step – you have to code it! That’s right. You wanted control, now you got it.

This code usually goes to a separate class or an Operation in Trailblazer, don’t leave it in the controller if you don’t have to.

def create
  file = params.delete(:image)
  user = User.create(params) # this is your code.
 
  # upload code:
  user.image(file) do |v|
    v.process!(:original)                            
    v.process!(:thumb)   { |job| job.thumb!("75x75#") } 
    v.process!(:public)  { |job| job.watermark! } 
  end
 
  user.save
end

Whoooa, slow down. What’s all that?

file = params.delete(:image)
user = User.create(params) # this is your code.

Well, the first lines is Rails code: We simply create a user object. This is where paperclip and friends would hook in an start their magic. Paperdragon hates magic, that’s why I delete the uploaded file from params.

Explicit Processing.

After that setup, we finally get to process images.

user.image(file) do |v|
  v.process!(:original)                            
  v.process!(:thumb)   { |job| job.thumb!("75x75#") } 
  v.process!(:public)  { |job| job.watermark! } 
end

Do I really need to explain that? Ok.

I use the #image method we talked about earlier and pass the uploaded file into it.

In the block, I can create different versions of the image. It is an convention to name the unprocessed file :original – everything else is up to you!

The job objects are actually Dragonfly::Job instances and give you the entire API for processing, converting, cropping, resizing, analysing images. It’s insane and beautiful!

An important thing here is: once you call process! it happens. It’s your decision whether the entire block goes to a background job, if you want to crop the thumb right away, do the rest later, and so on.

Make It Persistent!

After the block is run, and you hopefully created some images, the last line seems odd.

user.save

This is to save the metadata! Remember, we have that field image_meta_data in the User model. And that very field gets populated with data when processing has finished.

Let’s check out this field’s content before we call #save and after the processing block.

user.image(file) do |v|
  v.process! #...
end
 
user.image_meta_data 
#=> {original: {uid:   "original-logo.jpg", 
#               width: 240, height: 800},
#    thumb:    {uid:   "thumb-logo.jpg", 
#               width: 48, height: 48},
#   ..and so on..

After processing, paperdragon automatically updates this field, so it can find the images later.

This is a fundamentally different concept to paperclip, which uses 5 or more columns of the model to generate the address at run-time.

While this might save some disk space, it is a very dangerous concept: If any of these values change by only one byte, you’re gonna lose your image since the computed address will be wrong. Paperdragon goes the explicit way and saves the address in the metadata hash.

Rendering Images!

All this work is rewarded when rendering a beautifully cropped thumbnail.

user.image[:thumb].url

That’s ready to be used in an #img_tag. Not too hard, right?

Reprocessing.

This was uploading. Paperdragon also makes it extremely explicitly simple to reprocess image version. For instance, let’s assume the thumb had been re-cropped via the UI.

user.image do |v|
  v.reprocess!(:thumb, Time.now) do |job| 
    job.thumb!("#{params[:w]}x#{params[:h]}#") 
  end
end

Reprocessing has a similar semantic. Here, you don’t need to pass in a file as we already got it stored. Furthermore, I call #reprocess! which requires a second parameter: a fingerprint!

That’s right, paperdragon automatically adds a fingerprint to the file name when reprocessing (if you want it).

Ehm, And.. What Else?

I am getting bored as I am replicating paperdragon’s README which will tell you about all the other great features such as renaming images, deleting, S3 support, custom UIDs, paperclip compatibility, and more.

Check It Out!

Paperdragon‘s API requires you to do all that work. In turn, it gives you control.

We are very happy with it, our site literally processes thousands of photos every day and paperdragon gives us exactly the amount of control we need, along with an abstraction that is not painful, yet.

BTW: The best Origami paperdragon image in the comments wins a free installation!

2 Responses to “Paperdragon: Explicit Image Processing”


  1. Louis

    Ouuuuf Vipul, that’s a great one!

Leave a Reply