CarrierWave: создайте одно и то же уникальное имя файла для всех версий файлов - PullRequest
20 голосов
/ 03 августа 2011

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

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

Теперь, когда вы переопределяете метод store_dir вашего Uploader'а на что-то вроде следующего:

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, которое облегчает это?Есть ли не такой очевидный, но более чистый способ сделать это?Рабочий код - это хорошо, но рабочий код, который не плохо пахнет, лучше.

Ответы [ 2 ]

21 голосов
/ 03 августа 2011

Вы можете сделать что-то подобное в вашем файле 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 голосов
/ 25 июля 2017

Проверьте также решение от Wiki-перевозчика, доступного сейчас 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

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

...