| 1 | module FileColumn |
|---|
| 2 | module Validations #:nodoc: |
|---|
| 3 | |
|---|
| 4 | def self.append_features(base) |
|---|
| 5 | super |
|---|
| 6 | base.extend(ClassMethods) |
|---|
| 7 | end |
|---|
| 8 | |
|---|
| 9 | # This module contains methods to create validations of uploaded files. All methods |
|---|
| 10 | # in this module will be included as class methods into <tt>ActiveRecord::Base</tt> |
|---|
| 11 | # so that you can use them in your models like this: |
|---|
| 12 | # |
|---|
| 13 | # class Entry < ActiveRecord::Base |
|---|
| 14 | # file_column :image |
|---|
| 15 | # validates_filesize_of :image, :in => 0..1.megabyte |
|---|
| 16 | # end |
|---|
| 17 | module ClassMethods |
|---|
| 18 | EXT_REGEXP = /\.([A-z0-9]+)$/ |
|---|
| 19 | |
|---|
| 20 | # This validates the file type of one or more file_columns. A list of file columns |
|---|
| 21 | # should be given followed by an options hash. |
|---|
| 22 | # |
|---|
| 23 | # Required options: |
|---|
| 24 | # * <tt>:in</tt> => list of extensions or mime types. If mime types are used they |
|---|
| 25 | # will be mapped into an extension via FileColumn::ClassMethods::MIME_EXTENSIONS. |
|---|
| 26 | # |
|---|
| 27 | # Examples: |
|---|
| 28 | # validates_file_format_of :field, :in => ["gif", "png", "jpg"] |
|---|
| 29 | # validates_file_format_of :field, :in => ["image/jpeg"] |
|---|
| 30 | def validates_file_format_of(*attrs) |
|---|
| 31 | |
|---|
| 32 | options = attrs.pop if attrs.last.is_a?Hash |
|---|
| 33 | raise ArgumentError, "Please include the :in option." if !options || !options[:in] |
|---|
| 34 | options[:in] = [options[:in]] if options[:in].is_a?String |
|---|
| 35 | raise ArgumentError, "Invalid value for option :in" unless options[:in].is_a?Array |
|---|
| 36 | |
|---|
| 37 | validates_each(attrs, options) do |record, attr, value| |
|---|
| 38 | unless value.blank? |
|---|
| 39 | mime_extensions = record.send("#{attr}_options")[:mime_extensions] |
|---|
| 40 | extensions = options[:in].map{|o| mime_extensions[o] || o } |
|---|
| 41 | record.errors.add attr, "is not a valid format." unless extensions.include?(value.scan(EXT_REGEXP).flatten.first) |
|---|
| 42 | end |
|---|
| 43 | end |
|---|
| 44 | |
|---|
| 45 | end |
|---|
| 46 | |
|---|
| 47 | # This validates the file size of one or more file_columns. A list of file columns |
|---|
| 48 | # should be given followed by an options hash. |
|---|
| 49 | # |
|---|
| 50 | # Required options: |
|---|
| 51 | # * <tt>:in</tt> => A size range. Note that you can use ActiveSupport's |
|---|
| 52 | # numeric extensions for kilobytes, etc. |
|---|
| 53 | # |
|---|
| 54 | # Examples: |
|---|
| 55 | # validates_filesize_of :field, :in => 0..100.megabytes |
|---|
| 56 | # validates_filesize_of :field, :in => 15.kilobytes..1.megabyte |
|---|
| 57 | def validates_filesize_of(*attrs) |
|---|
| 58 | |
|---|
| 59 | options = attrs.pop if attrs.last.is_a?Hash |
|---|
| 60 | raise ArgumentError, "Please include the :in option." if !options || !options[:in] |
|---|
| 61 | raise ArgumentError, "Invalid value for option :in" unless options[:in].is_a?Range |
|---|
| 62 | |
|---|
| 63 | validates_each(attrs, options) do |record, attr, value| |
|---|
| 64 | unless value.blank? |
|---|
| 65 | size = File.size(value) |
|---|
| 66 | record.errors.add attr, "is smaller than the allowed size range." if size < options[:in].first |
|---|
| 67 | record.errors.add attr, "is larger than the allowed size range." if size > options[:in].last |
|---|
| 68 | end |
|---|
| 69 | end |
|---|
| 70 | |
|---|
| 71 | end |
|---|
| 72 | |
|---|
| 73 | IMAGE_SIZE_REGEXP = /^(\d+)x(\d+)$/ |
|---|
| 74 | |
|---|
| 75 | # Validates the image size of one or more file_columns. A list of file columns |
|---|
| 76 | # should be given followed by an options hash. The validation will pass |
|---|
| 77 | # if both image dimensions (rows and columns) are at least as big as |
|---|
| 78 | # given in the <tt>:min</tt> option. |
|---|
| 79 | # |
|---|
| 80 | # Required options: |
|---|
| 81 | # * <tt>:min</tt> => minimum image dimension string, in the format NNxNN |
|---|
| 82 | # (columns x rows). |
|---|
| 83 | # |
|---|
| 84 | # Example: |
|---|
| 85 | # validates_image_size :field, :min => "1200x1800" |
|---|
| 86 | # |
|---|
| 87 | # This validation requires RMagick to be installed on your system |
|---|
| 88 | # to check the image's size. |
|---|
| 89 | def validates_image_size(*attrs) |
|---|
| 90 | options = attrs.pop if attrs.last.is_a?Hash |
|---|
| 91 | raise ArgumentError, "Please include a :min option." if !options || !options[:min] |
|---|
| 92 | minimums = options[:min].scan(IMAGE_SIZE_REGEXP).first.collect{|n| n.to_i} rescue [] |
|---|
| 93 | raise ArgumentError, "Invalid value for option :min (should be 'XXxYY')" unless minimums.size == 2 |
|---|
| 94 | |
|---|
| 95 | require 'RMagick' |
|---|
| 96 | |
|---|
| 97 | validates_each(attrs, options) do |record, attr, value| |
|---|
| 98 | unless value.blank? |
|---|
| 99 | begin |
|---|
| 100 | img = ::Magick::Image::read(value).first |
|---|
| 101 | record.errors.add('image', "is too small, must be at least #{minimums[0]}x#{minimums[1]}") if ( img.rows < minimums[1] || img.columns < minimums[0] ) |
|---|
| 102 | rescue ::Magick::ImageMagickError |
|---|
| 103 | record.errors.add('image', "invalid image") |
|---|
| 104 | end |
|---|
| 105 | img = nil |
|---|
| 106 | GC.start |
|---|
| 107 | end |
|---|
| 108 | end |
|---|
| 109 | end |
|---|
| 110 | end |
|---|
| 111 | end |
|---|
| 112 | end |
|---|