How to: Create random and unique filenames for all versioned files - carrierwaveuploader/carrierwave GitHub Wiki

Both of the methods below are available from Ruby 1.8.7 onwards.

Note: SecureRandom.uuid is just used as an example. While it is not truly unique, for most applications it is safe to assume that it is unique.

SecureRandom.uuid is not in Ruby 1.8.7 but SecureRandom.hex is and might suffice for your needs.

Unique filenames

The following will generate UUID filenames in the following format:

1df094eb-c2b1-4689-90dd-790046d38025.jpg

someversion_1df094eb-c2b1-4689-90dd-790046d38025.jpg

class PhotoUploader < CarrierWave::Uploader::Base
  def filename
    "#{secure_token}.#{file.extension}"
  end

  protected
  def secure_token
    var = :"@#{mounted_as}_secure_token"
    model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
  end
end

#filename can be called again after the file has been stored (e.g. during recreate_versions!), at which point original_filename is nil. Keep the method free of any if original_filename guard, and don't read original_filename in its body — take the extension from file.extension (as above) and keep the token stable across calls (a model instance variable within one request, or a DB column across requests).

For mount_uploaders (multiple files), capture the original-name → token map in a before :cache hook while original_filename is still available, and never call original_filename after storage:

  before :cache, :assign_secure_token

  def filename
    "#{secure_token}.#{file.extension}" if secure_token
  end

  protected

  def token_map
    var = :"@#{mounted_as}_secure_tokens"
    model.instance_variable_get(var) || model.instance_variable_set(var, {})
  end

  def assign_secure_token(file)
    return unless file.respond_to?(:original_filename) && file.original_filename
    token_map[file.original_filename] ||= SecureRandom.uuid
  end

  def secure_token
    token_map[original_filename] if original_filename
  end

For names that must survive recreate_versions!, store token_map in a serialized DB column instead of a model instance variable.

Note

If you do recreate_versions! this method will encode the filename of the previously encoded name, which will result in a new name.

The new name will not be stored in the database!

In order to save the newly generated filename you have to call save! on the model after recreate_versions!.

To keep the same name across recreate_versions!, persist the token to a database column (see Random filenames below) rather than regenerating it. Do not guard #filename with if original_filename: that was required before CarrierWave 3.0, but original_filename is now cleared once the file is stored, so the guard makes #filename return nil afterwards — which breaks retrieval / recreate_versions! and triggers a warning. See #2708.

class AvatarUploader < CarrierWave::Uploader::Base
  def filename
    # `mounted_as` column holds the identifier once stored; reuse it so the
    # name stays stable across `recreate_versions!`.
    stored = model.read_attribute(mounted_as)
    return stored if stored.present?

    "#{secure_token}.#{file.extension}"
  end

  protected

  def secure_token
    var = :"@#{mounted_as}_secure_token"
    model.instance_variable_get(var) || model.instance_variable_set(var, SecureRandom.uuid)
  end
end

Random filenames

The following will generate hexadecimal filenames in the following format:

43527f5b0d.jpg

someversion_43527f5b0d.jpg

The length of the random filename is determined by the parameter to secure_token() within the filename method. The shorter the filename, the more chance of duplicates occurring. Unless you have a specific need for shorter filenames, it is recommended to use unique filenames instead (see above).

class PhotoUploader < CarrierWave::Uploader::Base
  def filename
     "#{secure_token(10)}.#{file.extension}"
  end

  protected
  def secure_token(length=16)
    var = :"@#{mounted_as}_secure_token"
    model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.hex(length/2))
  end
end

Note

If you're using the methods described above, it might be a good idea to store tokens in a database column:

  def secure_token(length = 16)
    model.image_secure_token ||= SecureRandom.hex(length / 2)
  end

Instance variables won't be persisted which means that if you're somehow manipulating existing images (e.g. cropping), they will be created under different filenames and not assigned to the model properly.

If you want to have the secure token changed each time a new file is uploaded for an existing image (e.g. to bust browser image caching):

  before :cache, :reset_secure_token

  def reset_secure_token(file)
    model.image_secure_token = nil
  end

Saving the Original Filename

If you want to save the original filename for future reference you need to create a column in your ORM. Then use the before :cache callback to put that name in your ORM. It is important to use the before :cache callback because SanitizedFile will alter the file name.

  # in `class PhotoUploader`
  before :cache, :save_original_filename
  def save_original_filename(file)
    model.original_filename ||= file.original_filename if file.respond_to?(:original_filename)
  end

(Related: How to: Use a timestamp in file names)

Special Note about Directory Names

When setting the name of your directory, it is very important to not use a special SecureRandom name because Carrierwave will not be able to delete, update and edit any of the images once they have been uploaded.

The stock recommendation is something like this:

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

Or if you don't want to go the sometimes tedious filename way:

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

  private

  def unguessable_reproducible_id
    secret = [ENV['CARRIERWAVE_SALT'], model.id].join('/')
    Digest::SHA256.hexdigest(secret)
  end

But do NOT attempt to do something like this:

  def store_dir
    "images/#{SecureRandom.uuid()}"
  end