‹ home

Validating Paperclip image dimensions in Rails

14 Sep 2012

I love using Paperclip for handling image uploads in my Rails apps. It’s super easy to use and plays nice with Amazon S3. It even has validators for attachment’s content-type, filesize & presence.

What it lacks is a built-in validator for image dimensions, but I’ve found out that it’s not so hard to build one yourself. In my last project, BathLiving, I needed the user to upload images of specific dimensions. These images where going to be used in image sliders, thumbnails etc. I didn’t want to use ImageMagick’s resizing/scaling options since I wanted the quality of the image to remain intact. So I had to force the admins to create images with the proper width & height in the first place.

Suppose this is our model:

# app/models/product.rb

class Product < ActiveRecord::Base
  attr_accessible :title, :image

  has_attached_file :image, styles: { thumb: '298x120#' }

  validates_attachment :image, presence: true

  validate :image_dimensions

  private

  def image_dimensions
    required_width  = 200
    required_height = 200
    dimensions = Paperclip::Geometry.from_file(image.queued_for_write[:original].path)

    errors.add(:image, "Width must be #{width}px") unless dimensions.width == required_width
    errors.add(:image, "Height must be #{height}px") unless dimensions.height == required_height
  end
end

I believe the code speaks for itself here. Pretty simple, right? We use #from_file that Paperclip::Geometry gives us to retrieve the dimensions of the image that the user is trying to upload, in other words, the image that’s queued for write.

But there’s one major drawback in this implementation: it’s not reusable. What if we need to validate the dimensions in other models too? We’d have to do the same thing over and over in our models.

Custom validators to the rescue. It looks like this:

# app/models/dimensions_validator.rb

class DimensionsValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    if record.send("#{attribute}?".to_sym)
      dimensions = Paperclip::Geometry.from_file(value.queued_for_write[:original].path)
      width = options[:width]
      height = options[:height]

      record.errors[attribute] << "Width must be #{width}px" unless dimensions.width == width
      record.errors[attribute] << "Height must be #{height}px" unless dimensions.height == height
    end
  end
end

First we check that the attachment is actually present (line 5). If it is, we perform the validations.

Now in our models we can simply do this:

validates :image, dimensions: { width: 200, height: 200 }

and Rails will automatically find our custom validator since it’s in the right place (app/models/) with the right name (dimensions_validator).

Now this implementation won’t work with ActiveRecord associations (eg. if the attachments are declared on a separate Image model), but for the common cases where the attachments and their parent records are in the same model it’s all good.


email · github · twitter · linkedin · keybase
PGP Fingerprint: D00B 5178 A5C0 C097