Подтвердить что ты не робот

CarrierWave: создать то же уникальное имя файла для всех версий файлов

Прежде чем я углубится в подробности, я пойду прямо: кто-нибудь понял, как заставить Carrierwave сохранять файлы с их именами как временную метку или любую произвольную строку, уникальную для каждого файла?

По умолчанию Carrierwave сохраняет каждый файл и его альтернативные версии в своем собственном каталоге (названном после идентификационного номера модели). Я не поклонник этого, потому что вместо одного каталога с 1000, ради использования большого круглого числа, файлы (в моем случае фотографии) в нем мы получаем один каталог с 1000 подкаталогами с одним или двумя файлами. Тьфу.

Теперь, когда вы переопределите метод Uploader store_dir, чтобы выглядеть примерно так:

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

вы получите точное поведение, которое я хочу. Все файлы (картинки) попадают в одну большую счастливую папку. Больше нет вложенных папок, когда они удаляются.

Есть только одна проблема. Коллизии файлов. Если вы загрузите delicious_cake.jpg дважды второй, он перезапишет первый, даже если это две разные фотографии вкусного торта! Это понятно, почему метод store_dir имеет дополнительный /#{model.id}, прикрепленный к концу возвращаемого значения.

Итак, что делать? Прочитав немного, я обнаружил, что в сгенерированном файле загрузчика есть явное решение, прокомментированное.

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

После небольшого поиска я нашел человека, который сделал следующее

def filename
  @name ||= "#{secure_token}.#{file.extension}" if original_filename
end

Это заставило меня задуматься, почему бы просто не сделать это

def filename
  @name ||= "#{(Time.now.to_i.to_s + Time.now.usec.to_s).ljust(16, '0')}#{File.extname(original_filename)}"
end

Это, когда все стало ужасно нарушено. Проблема заключается в том, что filename, по-видимому, вызывается для каждой версии файла, поэтому мы получаем имена файлов, такие как 1312335603175322.jpg и thumb_1312335603195323.jpg. Обратите внимание на небольшую разницу? Каждое имя файла основано на времени, когда filename была вызвана для этой конкретной версии. Это не будет делать вообще.

Затем я устал, используя model.created_at для основы метки времени. Только одна проблема, которая возвращает nil для первой версии, так как она еще не была помещена в базу данных.

После некоторого дальнейшего размышления я решил попробовать следующее в контроллере своих фотографий.

def create
  if params[:picture] and params[:picture][:image]
    params[:picture][:image].original_filename = "#{(Time.now.to_i.to_s + Time.now.usec.to_s).ljust(16, '0')}#{File.extname(params[:picture][:image].original_filename)}"
  end
  ...

Это переопределяет свойство original_filename до того, как Carrierwave даже доберется до него, сделав его меткой времени. Он делает именно то, что я хочу. Исходная версия файла заканчивается таким именем, как 1312332906940106.jpg, а версия миниатюры (или любая другая версия) заканчивается таким именем, как thumb_1312332906940106.jpg.

Но это кажется ужасным взломом. Это должно быть частью модели или еще лучше частью загрузчика, смонтированного на модели.

Итак, мой вопрос: есть ли лучший способ достичь этого? Я пропустил что-то важное с Carrierwave, что делает это проще? Есть ли не столь очевидный, но более чистый способ обойти это? Рабочий код хорош, но рабочий код, который плохо пахнет, лучше.

4b9b3361

Ответ 1

Вы можете сделать что-то подобное в вашем файле uploader, и оно также будет работать для файлов с версией (т.е. если у вас есть одно изображение, а затем создайте 3 другие миниатюрные версии одного и того же файла, все они будут иметь одинаковое имя, только с информацией о размере, добавленной к имени):

  # 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

Это создаст такое имя файла, например: 76_a9snx8b81js8kx81kx92.jpg где 76 - идентификатор модели, а другой бит - случайный SHA-шестнадцатеричный.

Ответ 2

Проверьте также решение из вики-версии несущей, доступной сейчас https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Use-a-timestamp-in-file-names

Вы можете указать временную метку в именах файлов, переопределяющих имя файла, как вы можете читать в документах Carrierwave:

 class PhotoUploader < CarrierWave::Uploader::Base
     def filename
       @name ||= "#{timestamp}-#{super}" if original_filename.present? and 
       super.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

Не забудьте запомнить результат в переменной экземпляра, или вы можете получить разные метки времени, записанные в базу данных и хранилище файлов.