Прежде чем вдаваться в подробности, я сразу перейду к делу: придумал ли кто-нибудь способ заставить Carrierwave сохранять файлы с их именами в виде меток времени или любой произвольной строки, уникальной для каждого файла?
По умолчанию Carrierwave сохраняет каждый файл и его альтернативные версии в своем собственном каталоге (названном по идентификационному номеру модели). Я не фанат этого, потому что вместо одного каталога с 1000, ради использования большого круглого числа файлов (в моем случае изображений) в нем мы получаем один каталог с 1000 подкаталогами, каждый с одним или двумя файлами. Уф.
Теперь, когда вы переопределяете метод 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
Это заставило меня задуматься, почему бы просто не сделать this
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, что упростило это? Есть ли не такой очевидный, но более чистый способ сделать это? Рабочий код - это хорошо, но рабочий код, который не плохо пахнет, лучше.