2013-08-27 1 views
3

Я использую несущую волну в проекте Rails 4 с хранилищем файлов для разработки и тестирования и хранения тумана (для хранения на Amazon S3) для производства.Carrierwave: file hash и идентификатор модели в filename/store_dir

Я хотел бы сохранить мои файлы с путями, как это:

/model_class_name/part_of_hash/another_part_of_hash/hash-model_id.file_extension

(пример: /images/12/34/1234567-89.png где 1234567 является SHA1 хэш содержимого файла и 89 является идентификатор соответствующей модели изображения в база данных).

То, что я пытался до сих пор это:

class MyUploader < CarrierWave::Uploader::Base 

    def store_dir 
    "#{model.class.name.underscore}/#{sha1_for(file)[0..1]}/#{sha1_for(file)[2..3]}" 
    end 

    def filename 
    "#{sha1_for(file)}-#{model.id}.#{file.extension}" if original_file 
    end 

    private 

    def sha1_for file 
     Digest::SHA1.hexdigest file.read 
    end 

end 

Это не работает, потому что:

  • model.id не доступен, когда filename называется
  • file не всегда доступны, когда store_dir является под названием

Так, приходя в мои вопросы:

  • возможно использовать модели иды/атрибутов в filename? This link говорит, что это не должно быть сделано; есть ли способ обойти это?
  • Можно ли использовать содержимое/атрибуты файлов в пределах store_dir? Я не нашел никакой документации по этому поводу, но мой опыт до сих пор говорит «нет» (см. Выше).
  • Как бы вы реализовали имя файла/каталога, чтобы получить что-то как можно ближе к тому, что я изложил в начале?

ответ

4
  • включая идентификатор в имени файла на создание не может быть возможным, так как имя файла хранится в базе данных, но идентификатор пока не доступен. Обходным путем (по общему признанию, весьма экстремальным) было бы использование временного значения для создания, а затем after_commit on: :create, перемещение файла и изменение имени в базе данных. Возможно, это будет возможно оптимизировать с помощью after_create, но я оставлю это до вас. (This где carrierwave фактически загружает файл.)

  • В том числе атрибутов файлов непосредственно в store_dir не представляется возможным, так как store_dir используется для расчета url - url потребует зная sha1, который требует наличия доступа к файлу, который требует знания URL-адреса и т. д. Обходной путь довольно очевиден: кешируйте атрибуты, в которых вы заинтересованы (в данном случае sha1) в записи базы данных модели, и используйте их в store_dir.

  • Простейший вариант подхода id-in-filename заключается в использовании некоторого другого значения, такого как uuid, и сохранения этого значения в базе данных. Есть несколько заметок об этом here.

+0

Я принял ваш ответ, так как он отвечает на каждую точку моего вопроса, спасибо. Если вы заинтересованы в моем конечном решении, ознакомьтесь с моим собственным ответом, детализирующим его ... – severin

2

Ответ Taavo строго отвечает на мои вопросы. Но я хочу быстро детализировать окончательное решение, которое я реализовал, поскольку оно может помочь кому-то другому ...

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

Итак, у меня были имена файлов, такие как filehash-randomstring.extension.

Поскольку несущая волна сохраняет имя файла в модели, я понял, что у меня уже есть хэш-файл, доступный в модели (в форме первой части имени файла). Поэтому я использовал это в пределах store_dir, чтобы создать путь в форме model_class_name/file_hash_part/another_file_hash_part.

Моя последняя реализация выглядит следующим образом:

class MyUploader < Carrierwave::Uploader::Base 

    def store_dir 

    # file name saved on the model. It is in the form: 
    # filehash-randomstring.extension, see below... 
    filename = model.send(:"#{mounted_as}_identifier") 

    "#{model.class.name.underscore}/#{filename[0..1]}/#{filename[3..4]}" 
    end 

    def filename 
    if original_filename 

     existing = model.send(:"#{mounted_as}_identifier") 

     # reuse the existing file name from the model if present. 
     # otherwise, generate a new one (and cache it in an instance variable) 
     @generated_filename ||= if existing.present? 
     existing 
     else 
     "#{sha1_for file}-#{SecureRandom.hex(4)}.#{file.extension}" 
     end 

    end 
    end 

    private 

    def sha1_for file 
     Digest::SHA1.hexdigest file.read 
    end 

end 
2

я наткнулся на такой же проблемой в последнее время, где model.id не был доступен еще при сохранении файла в БД, при создании uploader записи. Я нашел это решение. Я не уверен, уважает ли он принципы RESTful, я открыт для предложений.

Я изменил контроллер, так что сразу после создания изображения выполняется update_attributes, так что имя файла, включая существующее значение model.id, сохраняется в БД.

def create 
    @uploader = Uploader.new(uploader_params) 
    if @uploader.save 
     if @uploader.update_attributes(uploader_params) 
      render json: @uploader, status: :created 
     end 
    else 
     render json: @uploader.errors, status: :unprocessable_entity 
    end 
    end 
Смежные вопросы