Nie tak dawno temu opublikowałem wpis w którym pokazuję jak zrobić walidację minimalnego rozmiaru obrazka z wykorzystaniem Paperclipa. Podejście to było (i jest) proste, jednak okazało się niewystarczające (niewygodne) gdy mamy do czynienia z kilkoma plikami graficznymi na model. Z tego względu przebudowałem tę metodę, tak aby wydzielić ja do samego Paperclipa. Mimo że wyszła wersja 2.3.8 Paperclipa, sam korzystam z lekko zmodyfikowanej przeze mnie 2.3.6, która działa na Rails3. Kod ten powinien działać na każdej wersji Paperclipa.

module Paperclip
  module ClassMethods
    def validates_attachment_minimum_resolution name, options = {}
      validation_options = options.dup
      validates_each(name, validation_options) do |record, attr, value|
        unless record.errors.include?(name)
          m_width  = options[:width]
          m_height = options[:height]
          message = options[:message] || "must be bigger."
          message = message.call if message.is_a?(Proc)
          image = record.send(name)
          if image && image.queued_for_write[:original]
            dimensions = Paperclip::Geometry.from_file(image.queued_for_write[:original])
            if dimensions.width < m_width || dimensions.height < m_height
              if record.errors.method(:add).arity == -2
                record.errors.add(:"#{name}", message)
              else
                record.errors.add(:"#{name}", :inclusion, :default => options[:message], :value => value)
              end
            end
          end
        end
      end
    end
  end
end

Wrzucamy do /lib/extensions, podpinamy w /config/application.rb:

require 'extensions/paperclip'

I używamy. A jak działa? Załóżmy że mamy mieć miniaturkę (:thumb), która ma mieć rozdzielczość minimum 300×300 i być w formacie JPG:

  TYPES = ['image/jpeg', 'image/jpg', 'image/pjpeg']
  FILE_MAX_SIZE = 2048

  has_attached_file :thumb, :styles => {:mini => "120x95\#",
                  :standard => "190x150\#", :big => "300x300>"},
                  :url => "/images/models/thumb/:id_:style.:extension",
                  :default_style => :standard

  validates_attachment_presence :thumb,
    :message =>  'musi być dołączony'
  validates_attachment_size :thumb, :less_than => FILE_MAX_SIZE.kilobytes,
    :message =>  "musi mieć mniej niż #{FILE_MAX_SIZE} kb"
  validates_attachment_content_type :thumb, :content_type => TYPES,
    :message =>  "ma nieprawidłowy format"
  validates_attachment_minimum_resolution :thumb,
    :width => 300, :height => 300,
    :message => "mus mieć minimum 300px na 300px"

Ważne jest aby sprawdzać typ pliku przed sprawdzeniem rozmiarów, ponieważ walidacja ta nie uruchomi się jeśli plik zostanie uznany przez wcześniejsze walidacje za niepoprawny.

Warto zauważyć, że można to internacjonalizować. Mamy w kodzie taki fragment:

message = message.call if message.is_a?(Proc)

Dzięki któremu możemy zrobić w kodzie tak:

:message => lambda{I18n.t("activerecord.errors.messages.paperclip.res", {
    :width => 300, :height => 300})}

I mieć inny komunikat zależnie od języka.

Warto wspomnieć też, że walidacja ta (tak jak reszta paperclipowych) przeprowadzana jest tylko jeśli wymuszamy podstawową walidację obecności pliku:

  validates_attachment_presence :thumb

Aby uruchamiać walidację tylko jeśli plik został do formularza dołączony (ale formularz może być bez pliku), można zrobić tak:

  validates_attachment_presence :thumb,
    :message =>  "musi istnieć",
    :if => Proc.new { |imports| imports.thumb.file? }

Lub, można też dopisać sobie taką metodę do naszego rozszerzenia i będzie to za nas robione automatycznie:

    def validates_attachment_if_included name, options = {}
      options[:if] = Proc.new { |imports| imports.send(name).file? }
      validates_attachment_presence name, options
    end

Przykład:

  validates_attachment_if_included :thumb

Paperclip minimum resolution validation extension