Skip to content

Instantly share code, notes, and snippets.

@chrisbloom7
Created June 6, 2011 07:16
Show Gist options
  • Save chrisbloom7/1009861 to your computer and use it in GitHub Desktop.
Save chrisbloom7/1009861 to your computer and use it in GitHub Desktop.
A cheap knock off of the default validates_length_of validator, but checks the filesize of a Carrierwave attachment

Note that this validation runs both after the file is uploaded and after CarrierWave has processed the image. If your base uploader includes a filter to resize the image then the validation will be run against the resized image, not the original one that was uploaded. If this causes a problem for you, then you should avoid using a resizing filter on the base uploader and put any specific size requirements in a version instead.

So instead of this:

require 'carrierwave/processing/mini_magick'

class LogoUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

  process :quality => 80
  process :resize_to_limit => [800, 800]
  process :convert => 'png'
  
  # ...
end

Do this:

require 'carrierwave/processing/mini_magick'

class LogoUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

  process :convert => 'png'

  version :medium do
    process :quality => 80
    process :resize_to_limit => [800, 800]
  end

  # ...

end
# config/locales/en.yml
en:
errors:
messages:
wrong_size: "is the wrong size (should be %{file_size})"
size_too_small: "is too small (should be at least %{file_size})"
size_too_big: "is too big (should be at most %{file_size})"
# lib/file_size_validator.rb
# Based on: https://gist.github.com/795665
class FileSizeValidator < ActiveModel::EachValidator
MESSAGES = { :is => :wrong_size, :minimum => :size_too_small, :maximum => :size_too_big }.freeze
CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
def initialize(options)
if range = (options.delete(:in) || options.delete(:within))
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
options[:minimum], options[:maximum] = range.begin, range.end
options[:maximum] -= 1 if range.exclude_end?
end
super
end
def check_validity!
keys = CHECKS.keys & options.keys
if keys.empty?
raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
end
keys.each do |key|
value = options[key]
unless value.is_a?(Integer) && value >= 0
raise ArgumentError, ":#{key} must be a nonnegative Integer"
end
end
end
def validate_each(record, attribute, value)
raise(ArgumentError, "A CarrierWave::Uploader::Base object was expected") unless value.kind_of? CarrierWave::Uploader::Base
value = (options[:tokenizer] || DEFAULT_TOKENIZER).call(value) if value.kind_of?(String)
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
value ||= [] if key == :maximum
value_size = value.size
next if value_size.send(validity_check, check_value)
errors_options = options.except(*RESERVED_OPTIONS)
errors_options[:file_size] = help.number_to_human_size check_value
default_message = options[MESSAGES[key]]
errors_options[:message] ||= default_message if default_message
record.errors.add(attribute, MESSAGES[key], errors_options)
end
end
def help
Helper.instance
end
class Helper
include Singleton
include ActionView::Helpers::NumberHelper
end
end
@bosskovic
Copy link

I was getting "comparison of String with 0 failed" exception in the line 51 for the values larger then 1023, until I added precision:

errors_options[:file_size] = help.number_to_human_size check_value, :precision => 5

Now it works fine.

Ruby 2.0.0-p247
Rails 3.2.13

@szymon-przybyl
Copy link

why not merge it into CarrierWave? :)

@RKushnir
Copy link

I'm debugging the code because it's giving me undefined method '<=' for nil:NilClass), that's on the line next if value_size.send(validity_check, check_value).
The options I'm passing: validates :profile_img, file_size: {maximum: 2.megabytes.to_i}.

The following 2 lines don't make sense for me:

raise(ArgumentError, "A CarrierWave::Uploader::Base object was expected") unless value.kind_of? CarrierWave::Uploader::Base
value = (options[:tokenizer] || DEFAULT_TOKENIZER).call(value) if value.kind_of?(String)

Maybe I'm missing something, but value cannot be of type Uploader and String at the same time:

>   CarrierWave::Uploader::Base.new.kind_of? String
=> false 
> "abc".kind_of? CarrierWave::Uploader::Base
=> false 

@RKushnir
Copy link

I discovered this was caused by some coincidence: I was migrating from paperclip and some records already had uploads, so carrierwave was thinking they are valid. I'm hosting on Azure, so I used carrierwave-azure which makes an HTTP request to get the file size and returns nil, instead of 0, if the file is unavailable. The solution was to just clear the outdated values in the upload column. Maybe this helps someone in a similar situation.

Anyway, this validator is almost useless(besides making a human readable file size in the error message), as you can just use the standard Rails length validator.

@roberthimler
Copy link

Works for me the only problem is if the file is too big it ends up saving the file in my application's /uploads/tmp/ directory. Any ideas of how to avoid this from happening and delete the file?

@saeedSarpas
Copy link

What is the right way to set maximum file size amount for test env?
I did something like this:

file_size: { maximum: (Rails.env.test? ? 1.megabytes.to_i : 5.megabytes.to_i }

but I don't feel it's right.

@jmuheim
Copy link

jmuheim commented Jul 19, 2014

@saeedSarpas: I'm thinking about this, too. Is there a way to stub the validation within the test itself?

At least, I prettified your code a little:

validates :avatar, file_size: {maximum: (Rails.env.test? ? 5 : 100).kilobytes.to_i}

@heartfulbird
Copy link

Hi! I'm using carrierwave and file_size_validator
and I'm using sidekiq
and when I run "bundle exec sidekiq " in console
i see this errors
.../lib/file_size_validator.rb:5: warning: already initialized constant FileSizeValidator::MESSAGES
and the same about
FileSizeValidator::CHECKS
and ::DEFAULT_TOKENIZER
and ::RESERVED_OPTIONS

@musaffa
Copy link

musaffa commented Dec 11, 2014

file validators gem does file validations more elegantly. It also supports validations both before and after uploads.

@pioz
Copy link

pioz commented Apr 22, 2015

@vinayvinay
Copy link

Can anyone help me resolve this error that comes up while using this validator? For some reason the value.size calls .content_length on a nil.

lib/file_size_validator.rb:47:in `block in validate_each'
NoMethodError (undefined method `content_length' for nil:NilClass):
lib/file_size_validator.rb:42:in `each'
lib/file_size_validator.rb:42:in `validate_each'

@ch3m1c
Copy link

ch3m1c commented Nov 24, 2015

Anyone using this with multiple files upload from Carrierwave? :)

@osnysantos
Copy link

Im getting the same error as @vinayvinay

It seems that the asset was removed out of app and file_size_validator try to validate an nonexistent file.

@mariorcardoso
Copy link

@osnysantos I was experiencing the same error because the asset was not present (the url was broken). I added the the line next if value.present? && !(value.file.exists?) inside the validate_each loop so I don't get an exception when the file url is broken. It seems to work fine so far. If the url is broken I don't need to validate the file size.

checks.each do |key, validity_check|
      next unless check_value = options[key]

      value ||= [] if key == :maximum
      next if value.present? && !(value.file.exists?)

      value_size = value.size
      next if value_size.send(validity_check, check_value)

@pinty
Copy link

pinty commented Jun 27, 2017

Hello @chrisbloom7
As we would like to use your source code present on this page, is there any chance that we can get a license for the code written by you into the following file file_size_validator.rb ? We couldn't find any.
Thank you!

@alexanderadam
Copy link

alexanderadam commented Aug 5, 2017

Hey @pinty,

it's possible to replace it with file_validators gem mentioned above by it's author which has the MIT license.
Besides from that it's properly packaged, has specs and a dedicated issue tracker.

PS: I'm pretty sure I'm reading a document you (co-)authored 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment