CarrierWave: Create the same, unique filename for all versioned files
You can do something like this in your uploader
file, and it will also work for versioned files (i.e. if you have one image and then create 3 other thumbnail versions of the same file, they will all have the same name, just with size info appended onto the name):
# Set the filename for versioned files
def filename
random_token = Digest::SHA2.hexdigest("#{Time.now.utc}--#{model.id.to_s}").first(20)
ivar = "@#{mounted_as}_secure_token"
token = model.instance_variable_get(ivar)
token ||= model.instance_variable_set(ivar, random_token)
"#{model.id}_#{token}.jpg" if original_filename
end
This will create a filename like this for example: 76_a9snx8b81js8kx81kx92.jpg
where 76 is the model's id and the other bit is a random SHA hex.
How to keep the same filename in carrierwave when updating a file (in rails)
original_filename
in this context is the name of the file that was just received by the uploader. It's the file's original name.
By doing this:
"#{model.id}-#{original_filename}" if original_filename.present?
You're telling CarrierWave to use the new name of the file every time. It sounds like you want a given model to use the same filename every time. So the obvious option is simply to throw away the original_filename
altogether and use the model id by itself.
However, the comment above the filename
method in the auto-generated uploader code says this:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
Which leads us to this...
# Be careful using record ids as filenames. If the filename is stored in the database
# the record id will be nil when the filename is set. Don't use record ids unless you
# understand this limitation.
That is kind of a hitch. Unless you can guarantee that your model always has an id (which won't be true if it's new) then you shouldn't use model.id
to name your file.
I would suggest you generate a unique file_identifier
string once for each model on initialize (as long as the file_identifier
has not already been set), and just use this unique string as your filename, e.g.
class Something < ActiveRecord::Base
mount_uploader :image, SomeImageUploader
after_initialize do
unless self.file_identifier
self.file_identifier = SecureRandom.hex(32)
end
end
end
...and in your uploader class...
def filename
model.file_identifier if original_filename
end
Now your uploaded image will always overwrite the same file. But notice: you have just lost your image extension. That seems to be fine in my modern version of Firefox (it can guess by the looking at the image header) but it's probably not the most widely compatible solution. You could take the extension of the original_filename
and add it on there, but now you're back to your original problem if someone uploads a png and then uploads a jpg (the result would be two different files).
You could perform a conversion to a fixed format of your choice on upload, then the filename + extension would always be the same.
Honestly I think you'd have an easier time if you accept that files get a new name after every upload, and simply leave the old files hanging around for a set period of time to continue supporting users who have the old link. (How old are these links going to get anyway? If it's sitting in a cache, then why cache only the generated HTML without caching the image?) This is especially true if you ever plan to use a CDN service between your application and your users. The usual pattern is to version your files with a new unique filename every time, which the CDN will cache once and forever.
Carrierwave unique filename not being set
I'm not sure why the docs used ||=
operator in filename
method, but this way the unique file name will not be set unless @filename
is nil
, which doesn't seem to be the usual case. Using =
instead of ||=
(or not using assignment at all) seems to solve the issue.
def filename
@filename = "#{secure_token}.#{file.extension}" if original_filename.present?
# The following line works too
#"#{secure_token}.#{file.extension}" if original_filename.present?
end
Generate unique filename
This is one option:
def filename
random_string
end
protected
def random_string
@string ||= "#{SecureRandom.urlsafe_base64}.gif"
end
I agree carrierwave could a be a tad more intuitive.
CarrierWave: create 1 uploader for multiple types of files
I came across this, and it looks like an example of how to solve this problem: https://gist.github.com/995663.
The uploader first gets loaded when you call mount_uploader
, at which point things like if image?
or elsif video?
won't work, because there is no file to upload defined yet. You'll need the methods to be called when the class is instantiated instead.
What the link I gave above does, is rewrite the process
method, so that it takes a list of file extensions, and processes only if your file matches one of those extensions
# create a new "process_extensions" method. It is like "process", except
# it takes an array of extensions as the first parameter, and registers
# a trampoline method which checks the extension before invocation
def self.process_extensions(*args)
extensions = args.shift
args.each do |arg|
if arg.is_a?(Hash)
arg.each do |method, args|
processors.push([:process_trampoline, [extensions, method, args]])
end
else
processors.push([:process_trampoline, [extensions, arg, []]])
end
end
end
# our trampoline method which only performs processing if the extension matches
def process_trampoline(extensions, method, args)
extension = File.extname(original_filename).downcase
extension = extension[1..-1] if extension[0,1] == '.'
self.send(method, *args) if extensions.include?(extension)
end
You can then use this to call what used to be process
IMAGE_EXTENSIONS = %w(jpg jpeg gif png)
DOCUMENT_EXTENSIONS = %(exe pdf doc docm xls)
def extension_white_list
IMAGE_EXTENSIONS + DOCUMENT_EXTENSIONS
end
process_extensions IMAGE_EXTENSIONS, :resize_to_fit => [1024, 768]
For versions, there's a page on the carrierwave wiki that allows you to conditionally process versions, if you're on >0.5.4. https://github.com/jnicklas/carrierwave/wiki/How-to%3A-Do-conditional-processing. You'll have to change the version code to look like this:
version :big, :if => :image? do
process :resize_to_limit => [160, 100]
end
protected
def image?(new_file)
new_file.content_type.include? 'image'
end
Related Topics
Best Way to Cache a Response in Sinatra
What's the Best Way to Parse a Tab-Delimited File in Ruby
How to Set a Blank Value for an F.Select Form Field
How to Check If a Gem Is Installed
Listing the Names of Associated Models
Rails Activeadmin - Custom Association Select Box
Rails: Logging for Code in the Lib Directory
Comparing Times Only, Without Dates
Unit Test in Rails - Model with Paperclip
Passing Param Values to Redirect_To as Querystring in Rails
Ruby: Get All Keys in a Hash (Including Sub Keys)
How to Introspect Things in Ruby
Can't Convert Fixnum to String During Rake Db:Create
How to Get a List of All Tags While Using the Gem 'Acts-As-Taggable-On' in Rails (Not the Counts)
How to Render a Partial in Sinatra View (Haml in Haml)