Shrine: Retain original filename with version

Created on 18 Nov 2017  路  4Comments  路  Source: shrinerb/shrine

My issue looks like https://github.com/janko-m/shrine/issues/195 but it's a bit different. I want to keep the original filename when generating the location

uploads/
   original_filename.jpg
   original_filename_thumb.jpg
   original_filename_medium.jpg

With the version plugin the original filename is not persisted and path looks like that

uploads/
   original_filename.jpg
   minimagick_UID_thumb.jpg
   minimagick_UID_medium.jpg

The extract_filename(io).to_s does not return the original filename when working on versions. My current workaround is doing something like that.

  def generate_location(io, context = {})
    if [:original, nil].include? context[:version]
      @filename = File.basename(extract_filename(io).to_s, '.*')
    end
    extension   = ".#{io.extension}" if io.is_a?(UploadedFile) && io.extension
    extension ||= File.extname(extract_filename(io).to_s).downcase
    version = context[:version] && context[:version] == :original ? "" : "_#{context[:version]}"
    "#{context[:record].class.name.downcase.pluralize}/#{@filename}#{version}#{extension}"
  end

I persist the filename when processing the original filename but I would like to know if you have a better version for this. If there isn't and you think this problem is a "edge case" feel free to close this issue.

Most helpful comment

@hmistry I think you were definitely on the right track, the solution would be to retrieve the original filename from cached file. But I think it could be simplified a bit (this example is assuming your attachment attribute is named #file):

def generate_location(io, context)
  original_filename = context[:record]&.file&.original_filename || context[:metadata]["filename"]
  version_suffix    = "_#{context[:version]}" if context[:version] && context[:version] != :original
  basename          = File.basename(original_filename, ".*")
  extension         = File.extname(original_filename).downcase

  "#{context[:record].class.name.downcase.pluralize}/#{basename}#{version}#{extension}"
end

EDIT: One trick that's worth mentioning here is fetching original filename that was already extracted from the current io from context[:metadata].

All 4 comments

extract_filename() method gets the original_filename if io responds to that method or returns the io base filename. For versions, the file is a new temp file copy so its filename will have a unique generated string. I don't think io object holds any info on original filenames. For that we have to look at Shrine's stored metadata in the record.

Here's a solution I came up with:

  def generate_location(io, context)
    if [:small, :thumb].include? context[:version]
      file_data = JSON.parse context.dig(:record).file_data
      file_ext = File.extname(file_data.dig("metadata", "filename"))
      file_basename = File.basename(file_data.dig("metadata", "filename"), file_ext)
      location = file_basename + "_" + context[:version].to_s + file_ext
    else
      file_ext = File.extname(context.dig(:metadata, "filename"))
      file_basename = File.basename(context.dig(:metadata, "filename"), file_ext)
      location = file_basename + file_ext
    end
    context[:metadata]["filename"] = location
    location
  end

@hmistry I think you were definitely on the right track, the solution would be to retrieve the original filename from cached file. But I think it could be simplified a bit (this example is assuming your attachment attribute is named #file):

def generate_location(io, context)
  original_filename = context[:record]&.file&.original_filename || context[:metadata]["filename"]
  version_suffix    = "_#{context[:version]}" if context[:version] && context[:version] != :original
  basename          = File.basename(original_filename, ".*")
  extension         = File.extname(original_filename).downcase

  "#{context[:record].class.name.downcase.pluralize}/#{basename}#{version}#{extension}"
end

EDIT: One trick that's worth mentioning here is fetching original filename that was already extracted from the current io from context[:metadata].

@Grafikart But note that you probably want to at least add the record ID into the location, to allow users to upload files that have the same filename. Currently any new upload that has the same filename as an existing uploaded file will overwrite it.

Thanks for the detailed explanation! It works well between filename and custom generated location to create a location that maps to both multi-tenant needs and carrierwave migration.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

uurcank picture uurcank  路  10Comments

brunowego picture brunowego  路  4Comments

nynhex picture nynhex  路  6Comments

waclock picture waclock  路  8Comments

cilim picture cilim  路  10Comments