-
Notifications
You must be signed in to change notification settings - Fork 1.7k
How to: Create random and unique filenames for all versioned files
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.
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}" if original_filename.present?
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
Here is a proposition for when you upload multiple files for one instance:
def filename
"#{secure_token}.#{file.extension}" if original_filename.present?
end
def secure_token
media_original_filenames_var = :"@#{mounted_as}_original_filenames"
unless model.instance_variable_get(media_original_filenames_var)
model.instance_variable_set(media_original_filenames_var, {})
end
unless model.instance_variable_get(media_original_filenames_var).map{|k,v| k }.include? original_filename.to_sym
new_value = model.instance_variable_get(media_original_filenames_var).merge({"#{original_filename}": SecureRandom.uuid})
model.instance_variable_set(media_original_filenames_var, new_value)
end
model.instance_variable_get(media_original_filenames_var)[original_filename.to_sym]
end
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!
.
If you want to keep the previously encoded name, there is a workaround:
class AvatarUploader < CarrierWave::Uploader::Base
def filename
if original_filename
if model && model.read_attribute(mounted_as).present?
model.read_attribute(mounted_as)
else
# new filename
end
end
end
end
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}" if original_filename.present?
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
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
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)
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