public
Last active

A cheap knock off of the default validates_length_of validator, but checks the filesize of a Carrierwave attachment

  • Download Gist
README.md
Markdown

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
en.yml
YAML
1 2 3 4 5 6 7
# 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})"
file_size_validator.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
# 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

In your en.yml you spelled "too" wrong twice.

No worries, thank you for the validator :)

Is this before or after upload? Does it wait until the 10GB file has uploaded to say it's too big?

After upload. Carrierwave wouldn't be able to report the real file size until it was fully uploaded. AFAIK browsers don't send file size info and if they did it could easily be spoofed.

Ok thank you, maybe I can have it timeout if it goes too long.

You may try asking about this on http://stackoverflow.com. I think it's a good question of whether there is a reliable way of doing this before uploading. One thing I can think of is maybe some kind of flash uploader component that would be able to read the file being selected, check the file size, maybe make a query to the server for any other requirements, and do some pre-validation on the client side before the form is submitted. Let me know how you make out, I'm interested in hearing if there is a solution.

On the other hand, it's really only the user that is inconvenienced if they try to upload something that is 10GB when a smaller limit is clearly stated.

Thanks for your help. I just worry about tying up resources.

Having trouble getting this to work. I've added the class to lib, restarted my rails server, and am still able to upload a 5MB file with validates :picture, file_size: { maximum: 4.megabytes }

@emilford - It should be defined like this:

validates :picture,
  :file_size => { 
    :maximum => 4.megabytes.to_i
  }

Both syntaxes work...what I had was just the new 1.9.x hash syntax. I just noticed that a resize_to_fill was dropping my 5MB file in size, so the validation was never hit. Thanks for this.

where can i find a validator for mongoid? Thank you

Thanks man. Worked perfect

@hyperrjas This validator works for me with mongoid.
@chrisbloom7 Thank you

@ruseel For me it does not works for mongoid: I have in model:

require 'file_size_validator'
class Post
  include Mongoid::Document
  mount_uploader :posted, PostedUploader, mount_on: :posted_filename
  field :posted
  field :posted_cache
  field :remove_posted
  field :remote_posted_url
  validates :posted, :presence => true, :file_size => { :maximum => 0.5.megabytes.to_i  } 
end

and I upload since my pc with 5 mb for example, and the image its uploaded with 5 mb size.

I have in my posted_uploader.rb

require 'carrierwave/processing/mini_magick'
class PostedUploader < CarrierWave::Uploader::Base
 include CarrierWave::MiniMagick
 storage :grid_fs

 def store_dir
  "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
 end

process :resize_to_limit => [560, 560]
process :convert => :png

#versions
      version :medium do
        process :resize_to_fit => [200,380]
        process :convert => :png
      end

      version :big do
        process :resize_to_limit => [560, 560]
        process :convert => :png
      end

     version :thumb do
        process :resize_to_fill => [61, 61]
        process :convert => :png
      end 

      version :superthumb do
        process :resize_to_fill => [50, 50]
        process :convert => :png
      end 

    def extension_white_list
     %w(jpg jpeg gif png)
    end

    def filename
       if not super.nil?
        super.chomp(File.extname(super)) + '.png'
      end
    end

end

Where is the problem? Why the validate does not works and I can upload image greather than 0.5 mb?

Thank you!

If you need to pass in a method name as the value try this: https://gist.github.com/1862084

@kolaboratory Thank you for this lib, but in my model, with this does not works:

 validates :posted, :file_size => { :maximum => 0.5.megabytes.to_i  } 

What have I that write in my model?

Thank you again!

Hello,

I have the same issue as @hyperrjas. Added the validator into the /lib folder and getting the following err:
Unknown validator: 'FileSizeValidator'

Using Rails 3.2.1 (i'm new to Rails so this might be something very simple)

My Model code looks like this:

validates :screenshot,
    :presence => true,
    :file_size => {
        :maximum => 5.megabytes.to_i
    }

actually, I just added the YML piece and now I'm getting this ...
"undefined method `key?' for nil:NilClass"

thanks for your help.

--i

@imehesz - did you remember to require the validator in the model file?

require 'file_size_validator'
class Whatever < ActiveRecord::Base

If that's not the solution then you will need to post the full stack trace of the error, including line numbers and file names.

@chrisbloom7,

that's what it was! I knew it was something silly.
works like a charm ...

thanks,
--iM

@emilford - thanks for the tip about when the image is validated against. I've added a note to the gist to highlight this issue.

@chrisbloom7, sure thing.

Even through the validation is working, the image is still getting cached for me. Is there anyway around this?

I'm assuming you mean cached as in stored to the temp folder. No there is no way around that as CarrierWave has to store it somewhere first before the file can be available for validation. You could use an after_validation callback to cleanup the temp file if the validation fails, or have a worker process do that periodically on older temp files.

Chris,

Thanks for getting back to me. Do you know what methods to call to make this happen? I can't seem to figure out how to get those to clean up. I have tried various things and they haven't worked. I would like to encapsulate everything on the Mounted Uploader, but not sure if that would work either.

Before the image is saved the full path to the temp image is stored in the attribute you've mounted the uploader on. So for instance:

 > i = Contest.new
 => #<Contest > 
 > i.image = File.open(Rails.root.join('spec', 'support', 'images', '0.5_mb.jpg'))
 => #<File:/Users/chrisbloom7/Projects/foo/spec/support/images/0.5_mb.jpg> 
 > i.image
 => /Users/chrisbloom7/Projects/foo/tmp/uploads/development/20120416-1201-4288-3087/0.5_mb.jpg 

So in your after_validation callback method, check to see if the instance's errors hash contains a key for your mounted attribute, and if so delete the image in the specified path.

Perfect...thank you!

You might also try posting to the CW Google Group to see if the Uploaders have a built in mechanism for handling this.

Hi guys, I get this error for spanish language:

I18n::MissingTranslationData in ItemsController#create
translation missing: es.number.human.storage_units.format

lib/file_size_validator.rb:51:in `block in validate_each'
lib/file_size_validator.rb:42:in `each'
lib/file_size_validator.rb:42:in `validate_each'
app/controllers/items_controller.rb:76:in `block in create'
app/controllers/items_controller.rb:75:in `create'

How can I fix this error for differents languages?
Thank you

@hyperrjas Try 'rails-i18n' gem in your Gemfile.

Edit: Try @ruseel's suggestion first

@hyperrjas Copy the entries from the config/locales/en.yml file above into config/locales/es.yml and provide the proper translations.

Thank you the @ruseel's suggestion has solved the problem :D. Thank you very much!

I'm getting an exception (unknown validator) when running rspec tests - is there a place I should include the file_size_validator somewhere in rspec? Thanks

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

why not merge it into CarrierWave? :)

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 

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.

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?

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.