Carrierwave: Custom file validations inside custom Uploaders

Updated . Posted . Visible to the public. Repeats.

Carrierwave's BaseUploader can have some validations that you can use by overriding a certain method, which's expected name is hard coded. A popular example is extension_allowlist, which returns an array of strings and let's you only upload files that have a filename with an extension that matches an entry in that array. Another useful validation can be size_range, which gives you a little bit of control over how your storage gets polluted.

This is often good enough, but some times you need to validate special cases.

Validations that are not supported out of the box

Let's say, you want to be strict about things and do not trust a user to name their files correctly. You might want to validate the MIME type in addition to the name extension.

Solution 1

The solution you will see most often Show archive.org snapshot is that the model that has a Carrierwave Uploader mounted will take care of the validation:

class User < ActiveRecord::Base
  mount_uploader :portrait, ImageUploader
  validate :valid_content_type

  private
  
  def valid_content_type
    errors.add(:portrait, "is an invalid file type") unless %w(image/jpeg image/png).include? file.sanitized_file.content_type
  end
end

class ImageUploader < BaseUploader
end

This works perfectly fine, but now every model which uses ImageUploader to store images will need to take care of validating. Of course, you could have a concern or a trait Show archive.org snapshot for this, but everyone who works on the project will need to know that the trait exists, when they write code. This seems not to be the right place, right?

Solution 2 (preferred)

No one on the internet (and I wonder why) tells you that you can have such validations directly on the Uploader itself, by hooking into a callback Show archive.org snapshot :

class User < ActiveRecord::Base
  mount_uploader :portrait, ImageUploader
end

class ImageUploader < BaseUploader
  before :cache, :check_mimetype
  
  private
  
  def check_mimetype(file)
    unless allowed_mime_types.include? file.content_type
      raise CarrierWave::IntegrityError, 'is an invalid file type'
    end
  end
  
  def allowed_mime_types
    %w(image/jpeg image/png)
  end
end

This will behave like the solution before: User#portrait will get an expected error message and the User instance will be invalid when attaching a portrait with a wrong file.

Hint

Obviously, this will work with a lot of considerable cases where you want to validate incoming files.

Now you and your colleagues can never forget validating the MIME type again.

Jakob Scholz
Last edit
Tobias Kraze
License
Source code in this card is licensed under the MIT License.
Posted by Jakob Scholz to makandra dev (2023-02-27 06:59)