CarrierWave Multi-uploader Renames Existing Files

74 views Asked by At

I have followed the CarrierWave documentation for multi-uploaders and you can upload multiple files as expected, however if the user uploads files a second time the original files get moved from file.png to file(2).png, but the database record does not get updated, thereby preventing the image from being loaded.

How can I either prevent the existing files from being moved or update my model with the updated filename?

class ProfilePhotoUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{model.id}/#{mounted_as}"
  end

  # Create different versions of your uploaded files:
  version :thumb do
    process resize_to_fit: [250, 250]
  end

  # Add an allowlist of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  def extension_allowlist
    %w(jpg jpeg gif png)
  end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  def filename
    original_filename if original_filename.present?
  end
end

Controller:

profile_params = params.require(:profile).permit([portraits: []])
@profile.portraits += profile_params[:portraits] if profile_params[:portraits]

Model:

Class Profile < ApplicationRecord
    ...
    mount_uploaders :portraits, ProfilePhotoUploader
    ...
end
1

There are 1 answers

4
VonC On BEST ANSWER

How can I either prevent the existing files from being moved or update my model with the updated filename?

One approach would be to override the filename method in your uploader class to make sure the original filename remains unchanged even if the same file is uploaded again.
(you have a similar approach in carrierwaveuploader/carrierwave issue 961 for a different problem)

def filename
  @name ||= "#{timestamp}-#{super}" if original_filename.present?
end

def timestamp
  var = :"@#{mounted_as}_timestamp"
  model.instance_variable_get(var) or model.instance_variable_set(var, Time.now.to_i)
end

And, whenever a file is uploaded, you would update the model with the new filename in your controller.

def update
  profile_params = params.require(:profile).permit([portraits: []])
  @profile.portraits += profile_params[:portraits] if profile_params[:portraits]
  @profile.save
  @profile.portraits.each_with_index do |portrait, index|
    @profile.portraits[index].retrieve_from_store!(File.basename(portrait.file.file))
  end
end

Your code would be:

# Uploader
class ProfilePhotoUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

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

  version :thumb do
    process resize_to_fit: [250, 250]
  end

  def extension_allowlist
    %w(jpg jpeg gif png)
  end

  def filename
    @name ||= "#{timestamp}-#{super}" if original_filename.present?
  end

  def timestamp
    var = :"@#{mounted_as}_timestamp"
    model.instance_variable_get(var) or model.instance_variable_set(var, Time.now.to_i)
  end
end

# Controller
def update
  profile_params = params.require(:profile).permit([portraits: []])
  @profile.portraits += profile_params[:portraits] if profile_params[:portraits]
  @profile.save
  @profile.portraits.each_with_index do |portrait, index|
    @profile.portraits[index].retrieve_from_store!(File.basename(portrait.file.file))
  end
end

# Model
Class Profile < ApplicationRecord
    ...
    mount_uploaders :portraits, ProfilePhotoUploader
    ...
end

The OP Kevin adds in the comments:

OK, this works, however with each upload the filename gets prefixed with a new timestamp, which will eventually overload the filename.
My first inclination is to also add a unique string, eg zzzz and remove everything before it, but that seems pretty clunky.

For now I'm going to do this:

@name ||= "#{timestamp}-#{super.gsub(/^\d\d\d\d\d\d\d\d\d\d-/, "")}"