Skip to content

Instantly share code, notes, and snippets.

@audionerd
Created October 24, 2014 21:47
Show Gist options
  • Save audionerd/4703b4ed3ddad82999f7 to your computer and use it in GitHub Desktop.
Save audionerd/4703b4ed3ddad82999f7 to your computer and use it in GitHub Desktop.
Replacing Paperclip file basename with UUID on upload (is there a better way?)
# replace file basename with a UUID when uploaded
# e.g.: avatar.jpg becomes 41bf830f-80cf-4efe-ad90-3b29bc6708b5.jpg
# but don't regenerate a UUID each time file url is accessed
class User < ActiveRecord::Base
has_attached_file :avatar, path: ":basename.:extension"
before_validation { set_avatar_file_name }
def set_avatar_file_name
# replace any NEW filename with a UUID
# NOTE: if user uploads a new image with the exact filename as the most recently assigned UUID, this will break!
if avatar_file_name_changed?
set_avatar_file_name_to_uuid
end
end
def set_avatar_file_name_to_uuid
self.avatar_file_name = SecureRandom.uuid + File.extname(avatar_file_name)
end
end
@audionerd
Copy link
Author

Turns out Paperclip provides a before_post_process callback which simplifies this a bit.

Ended up with something along these lines, which I can use for any Paperclip attachment:

# app/models/concerns/has_custom_file_name.rb
#   via https://groups.google.com/d/msg/paperclip-plugin/xFki0Nwv7oM/1Ev1m4uskeYJ
#
# Currently this class is only used for generating UUID filenames
# The callback is always `generate_uuid_for_file_name`
#
# USAGE
#   class User < ActiveRecord::Base
#     include HasCustomFileName
#
#     has_attached_file :avatar, path: ":basename.:extension"
#     has_custom_file_name :avatar, :generate_uuid_for_file_name
#   end
#

module HasCustomFileName
  extend ActiveSupport::Concern

  def generate_uuid_for_file_name(file_name)
    "%s%s" % [SecureRandom.uuid, File.extname(file_name).downcase]
  end

  module ClassMethods
    def has_custom_file_name attachment_name, callback
      send(:"before_#{attachment_name}_post_process", :"set_#{attachment_name}_file_name")

      define_method :"set_#{attachment_name}_file_name" do |*args|
        old_file_name = self.send(:"#{attachment_name}_file_name")
        new_file_name = self.send(callback, old_file_name)
        self.send(attachment_name).instance_write(:file_name, new_file_name)
      end
    end
  end
end

@audionerd
Copy link
Author

Updated the above to show each attachment should get its OWN callback, e.g.:
before_#{attachment_name}_post_process

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment