Read more

CarrierWave: Default Configuration and Suggested Changes

Dominik Schöler
February 24, 2021Software engineer at makandra GmbH

CarrierWave comes with a set of default configuration options which make sense in most cases. However, you should review these defaults and adjust for your project wherever necessary.

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

You will also find suggestions on what to change below.

Understanding the default configuration

Here is the current default config for version 2 Show archive.org snapshot :

config.permissions = 0644
config.directory_permissions = 0755
config.storage_engines = {
  :file => "CarrierWave::Storage::File",
  :fog  => "CarrierWave::Storage::Fog"
}
config.storage = :file
config.cache_storage = nil # (1)
config.fog_attributes = {}
config.fog_credentials = {}
config.fog_public = true
config.fog_authenticated_url_expiration = 600
config.fog_use_ssl_for_aws = true
config.fog_aws_accelerate = false
config.store_dir = 'uploads'
config.cache_dir = 'uploads/tmp'
config.delete_tmp_file_after_storage = true
config.move_to_cache = false
config.move_to_store = false
config.remove_previously_stored_files_after_update = true
config.downloader = CarrierWave::Downloader::Base
config.ignore_integrity_errors = true # (2)
config.ignore_processing_errors = true
config.ignore_download_errors = true
config.validate_integrity = true # (3)
config.validate_processing = true
config.validate_download = true
config.root = lambda { CarrierWave.root }
config.base_path = CarrierWave.base_path
config.enable_processing = true
config.ensure_multipart_form = true

Notes

  1. If no cache_storage is set, CarrierWave uses the configured storage for caching. Only set a value if you need a different type of cache storage.
  2. ignore_*_errors means "do not raise exceptions on error" (during caching). If you're performing uploads async, i.e. uploading files to cache and using the cache key to later store the upload to a model, you may set ignore_*_errors to false. This way, you'll be informed about errors already during upload, instead of later during model save.
  3. Your ActiveRecord instances will receive validation errors where validate_* = true.

"Integrity" is about valid files, e.g. matching extension white- or blacklists. "Processing" is about process calls, e.g. for changing image resolution. "Download" is when CarrierWave loads a file from a remote location via remote_<mounted_as>_url.

Suggested changes

  • We strongly suggest some kind of nested directory structure for performance reasons, unless you know you will be storing only a few files.
    For that, implement a store_dir instance method in your uploader (or an ApplicationUploader, if you have one). We have a separate card on how to do that.
    We suggest you also clear the store_dir config setting to avoid any confusion:

    config.store_dir = nil # Configured in the uploader
    
  • Do not implement cache_dir as an instance method on your uploader classes.
    While that works for uploading files and re-displaying uploaded (but not yet fully stored) files on form round trips, it breaks CarrierWave's clean_cached_files! method (see next item).
    If you want to specify a custom directory, just set it in the configuration:

    config.cache_dir = ...
    
  • Enable cleaning up old files from the cache directory. CarrierWave provides CarrierWave::Uploader::Base.clean_cached_files! for that already, but you need to call regularly from your preferred scheduler. For example, when using whenever, you should have something like this in your schedule.rb:

    every :day, at: '05:00', roles: [:cron] do
      runner 'ApplicationUploader.clean_cached_files!'
    end
    
  • When handling large files, consider enabling these options:

    config.move_to_cache = true
    config.move_to_store = true
    

    We have a separate card about that.

  • Store test files separately. Also add support for parallel tests. You can easily do that by setting config.root:

    config.root = "#{Rails.public_path}/system/#{Rails.env}#{ENV['TEST_ENV_NUMBER']}".freeze
    
  • For debugging purposes (e.g. trying to hunt down a staging bug locally), it might make sense to allow reading files from a separate environment. You you could read from an ENV variable instead of using your Rails.env.

Suggested configuration

In total, here is a suggested configuration that you can put into config/initializers/carrierwave.rb:

UPLOADER_ENV = (ENV['UPLOADER_ENV'] || Rails.env.to_s).freeze

CarrierWave.configure do |config|
  config.root = "#{Rails.public_path}/system/#{UPLOADER_ENV}#{ENV['TEST_ENV_NUMBER']}".freeze
  config.cache_dir = File.join(config.root, 'upload_cache').freeze
  config.store_dir = nil # Configured in the uploader

  # CarrierWave's `base_path` is not supposed to reference a file system path, but is used for URL generation.
  # Hence, it needs to be relative to the public directory, and start with a slash.
  config.base_path = config.root.delete_prefix(Rails.public_path.to_s).freeze
  
  # Optional: Move files instead of copying. Improves performance when dealing with large files, but may introduce caveats.
  # config.move_to_cache = true
  # config.move_to_store = true
end

And don't forget about the recurring task to clean cached files.

Dominik Schöler
February 24, 2021Software engineer at makandra GmbH
Posted by Dominik Schöler to makandra dev (2021-02-24 16:55)