Это вторая часть из серии постов о Shrine. Цель этой серии статей – показать преимущества Shrine над существующими загрузчиками файлов.
В предыдущем посте я говорил о том что мотивировало меня на создание Shrine. В этой статье я хочу показать вам фундамент, на котором основан Shrine — хранилище, загрузчик и загруженные файлы.
«Хранилище» Shrine — это plain Ruby-объект, который инкапсулирует управление файлами в определенном сервисе хранения (файловая система, S3 и.т.д). Хранилище должно содержать следующие 5 методов:
Хранилища Shrine конфигурируются напрямую, передавая опции в конструктор (позаимствовано у Refile) и должны быть зарегистрированы в
В настоящее время для Shrine имеются поддержка файловой системы, S3, Fog, Flickr, Cloudary, Transloadit, Uploadcare, Imgix, GridFS и SQL, так что выбирайте.
Вы также можете легко написать свое хранилище, для этого есть руководство и линтер, который автоматически проверит, правильно ли все работает.
Загрузчики являются подклассами Shrine, они инкапсулируют логику выгрузки определенного вложения (как у CarrierWave).
Объекты Uploader выступают в качестве оберток вокруг хранилища, в них выполняется вся логика для загрузки, которая является общей для любого хранилища:
Создание экземпляра загрузчика с установленным параметром хранилища:
Загрузчики не знают о моделях; Они только работают с файлом, который будет загружен на вход, и возвращают представление загруженного файла на выходе. Поскольку это предполагает, что загрузчики являются stateless, это делает их поведение очень предсказуемым.
Когда файл загружается через загрузчик, метод
Так как этот объект знает, куда он был загружен, он может предоставить много полезных методов:
Этот объект определяется только хешем. Поскольку на хранилище можно ссылаться по его установленному параметру, этот хэш теперь может быть сериализован в JSON и сохранен в столбце базы данных.
Объекты
Shrine может загрузить любой объект типа
Кроме того,
Shrine поставляется с небольшим ядром (<500 строк кода), которое обеспечивает необходимую функциональность. Любые дополнительные функции могут быть загружены через плагины. Это дает вам гибкость в выборе именно того, что и как должен делать для вас Shrine, и загружать код только для функционала, который вы используете.
Shrine поставляется с более чем 35 плагинами, и вы легко можете написать свои собственные. Плагиновая система Shrine — это адаптация Roda, о которой я писал в прошлом.
Кроме того, загрузчики Shrine могут наследоваться (в отличие от CarrierWave).
Большинство библиотек для загрузки файлов, имеют довольно монструозные зависимости.
Shrine, с другой стороны, имеет только одну обязательную но легкую зависимость — Down. Down — это net/http обертка для загрузки файлов, которая улучшает open-uri и поддерживает стриминг, и используется почти всеми хранилищами Shrine.
Кроме того, Shrine в целом загружается очень быстро, потому что вы загружаете код только для функционала, который вы используете. Для других загрузчиков требуется загрузить код для всего функционала, которые может вам не понадобиться. К примеру, Shrine загружается в 35 раз быстрее CarrierWave без загруженных плагинов и в 7 раз быстрее со всеми загруженными (исходник) плагинами.
Каждый высокоуровневый интерфейс должен иметь хороший фундамент. Таким образом, c каким бы уровнем абстракции вы не работали, вы всегда сможете понять, что происходит. Основа Shrine состоит из классов
Оригинал: https://twin.github.io/better-file-uploads-with-shrine-uploader/
Cтатьи из оригинальной серии в блоге автора библиотеки:
В предыдущем посте я говорил о том что мотивировало меня на создание Shrine. В этой статье я хочу показать вам фундамент, на котором основан Shrine — хранилище, загрузчик и загруженные файлы.
Хранилище
«Хранилище» Shrine — это plain Ruby-объект, который инкапсулирует управление файлами в определенном сервисе хранения (файловая система, S3 и.т.д). Хранилище должно содержать следующие 5 методов:
class MyStorage
def upload(io, id, **options)
# выгружает `io` в указанный `id`
end
def url(id)
# возвращает URL-адрес файла с `id`
end
def open(id)
# возвращает файл по адресу `id` в качестве объекта типа IO
end
def exists?(id)
# возвращает, существование файла в хранилище
end
def delete(id)
# удаляет соответствующий файл из хранилища
end
end
Хранилища Shrine конфигурируются напрямую, передавая опции в конструктор (позаимствовано у Refile) и должны быть зарегистрированы в
Shrine.storages
:
Shrine.storages[:s3] = Shrine::Storage::S3.new(
access_key_id: "abc",
secret_access_key: "xyz",
region: "eu-west-1",
bucket: "my-bucket",
)
В настоящее время для Shrine имеются поддержка файловой системы, S3, Fog, Flickr, Cloudary, Transloadit, Uploadcare, Imgix, GridFS и SQL, так что выбирайте.
Вы также можете легко написать свое хранилище, для этого есть руководство и линтер, который автоматически проверит, правильно ли все работает.
Загрузчик
Загрузчики являются подклассами Shrine, они инкапсулируют логику выгрузки определенного вложения (как у CarrierWave).
class ImageUploader < Shrine
# image uploading logic goes here
end
Объекты Uploader выступают в качестве оберток вокруг хранилища, в них выполняется вся логика для загрузки, которая является общей для любого хранилища:
- Обработка
- Извлечение метаданных
- Генерация локации для файла
- Загрузка (на этом этапе происходит обращение к хранилищу)
- Закрытие загруженного файла
Создание экземпляра загрузчика с установленным параметром хранилища:
Shrine.storages[:disk] = Shrine::Storage::FileSystem.new(...)
uploader = ImageUploader.new(:disk)
uploader.upload(image) #=> #<Shrine::UploadedFile>
Загрузчики не знают о моделях; Они только работают с файлом, который будет загружен на вход, и возвращают представление загруженного файла на выходе. Поскольку это предполагает, что загрузчики являются stateless, это делает их поведение очень предсказуемым.
Загруженные файлы
Когда файл загружается через загрузчик, метод
#upload
возвращает объект Shrine::UploadedFile
. Этот объект является полным представлением файла, который был загружен в хранилище.uploaded_file = uploader.upload(image) #=> #<Shrine::UploadedFile>
uploaded_file.id #=> "43ksd9gkafg0dsl.jpg"
uploaded_file.storage #=> #<Shrine::Storage::FileSystem>
uploaded_file.metadata #=> {...}
Так как этот объект знает, куда он был загружен, он может предоставить много полезных методов:
uploaded_file.url # generates the URL
uploaded_file.download # downloads the file to the disk
uploaded_file.exists? # asks the storage if file exists
uploaded_file.open { |io| ... } # opens the file for reading
uploaded_file.delete # deletes the file from the storage
Этот объект определяется только хешем. Поскольку на хранилище можно ссылаться по его установленному параметру, этот хэш теперь может быть сериализован в JSON и сохранен в столбце базы данных.
uploaded_file.data #=>
# {
# "id" => "df9fk48saflg.jpg",
# "storage" => "disk",
# "metadata" => {...}
# }
uploaded_file.to_json #=> '{"id":"df9fk48saflg.jpg","storage":"disk","metadata":{...}}'
Объекты
Shrine::UploadedFile
не зависят от загрузчиков. Это значительное отличие от CarrierWave и Paperclip, которые имеют такую зависимость с классами CarrierWave::Uploader::Base
и Paperclip::Attachment
.Абстракция IO
Shrine может загрузить любой объект типа
IO
, который отвечает на #read
, #size
, #rewind
, #eof?
И #close
(как у Refile). Определяя этот строгий интерфейс, каждая функция Shrine теперь знает, что они могут полагаться только на эти методы, а это значит, что они будут работать правильно независимо от того, загружаете ли вы типы File
, StringIO
, ActionDispatch::Http::UploadedFile
, Rack
или удаленные файлы, которые загружаются стримом.Кроме того,
Shrine::UploadedFile
сам по себе является объектом типа IO
, обертывая любой загруженный файл под тем же унифицированным интерфейсом. Это делает перемещение файла с одного хранилища на другое действительно удобным. Кроме того, это позволяет оптимизировать некоторые закачки путем пропуска процесса скачивания и повторной загрузки, например, использовать копию S3, если оба файла из S3, или просто отправить запрос URL, если хранилище поддерживает его.cache = ImageUploader.new(:s3_temporary)
cached_file = cache.upload(image)
store = ImageUploader.new(:s3_permanent)
store.upload(cached_file) #=> performs an S3 COPY request
Система плагинов
Shrine поставляется с небольшим ядром (<500 строк кода), которое обеспечивает необходимую функциональность. Любые дополнительные функции могут быть загружены через плагины. Это дает вам гибкость в выборе именно того, что и как должен делать для вас Shrine, и загружать код только для функционала, который вы используете.
# Loads the processing feature from "shrine/plugins/logging.rb"
Shrine.plugin :logging, logger: Rails.logger
Shrine поставляется с более чем 35 плагинами, и вы легко можете написать свои собственные. Плагиновая система Shrine — это адаптация Roda, о которой я писал в прошлом.
Кроме того, загрузчики Shrine могут наследоваться (в отличие от CarrierWave).
Shrine.plugin :logging # enables logging for all uploaders
class ImageUploader < Shrine
plugin :backup # stores backups only for this uploader (and its descendants)
end
Зависимости
Большинство библиотек для загрузки файлов, имеют довольно монструозные зависимости.
CarrierWave
- ActiveSupport – Я действительно не хочу всех этих манки-патчей
- ActiveModel – Почему бы не выполнять валидацию без библиотек?
- MIME::Types – Лучше определить MIME-тип из содержимого файла
Paperclip
- ActiveSupport – Опять же, я хочу иметь возможность не иметь никаких манки-патчей
- ActiveModel – Окей, любом случае, как ActiveModel, так и ActiveSupport требуются для ActiveRecord
- Cocaine – Open3 монструозная библиотека для запуска командной оболочки
- MIME::Types – Обнаружение подмены MIME-типа имеет проблемы
- MimeMagic – достаточно утилиты file
Refile
- RestClient – Монструозное решение для простой загрузки файлов
- Sinatra – Это нормально, хотя Roda – более легкая альтернатива
- MIME::Types – Лучше определить MIME-тип из содержимого файла
Shrine, с другой стороны, имеет только одну обязательную но легкую зависимость — Down. Down — это net/http обертка для загрузки файлов, которая улучшает open-uri и поддерживает стриминг, и используется почти всеми хранилищами Shrine.
Кроме того, Shrine в целом загружается очень быстро, потому что вы загружаете код только для функционала, который вы используете. Для других загрузчиков требуется загрузить код для всего функционала, которые может вам не понадобиться. К примеру, Shrine загружается в 35 раз быстрее CarrierWave без загруженных плагинов и в 7 раз быстрее со всеми загруженными (исходник) плагинами.
Итоги
Каждый высокоуровневый интерфейс должен иметь хороший фундамент. Таким образом, c каким бы уровнем абстракции вы не работали, вы всегда сможете понять, что происходит. Основа Shrine состоит из классов
Storage
, Shrine
и Shrine::UploadedFile
, каждый из которых имеет четко определенный интерфейс и обязанность.Оригинал: https://twin.github.io/better-file-uploads-with-shrine-uploader/
Cтатьи из оригинальной серии в блоге автора библиотеки:
Поделиться с друзьями