Фрагменты кода tl; dr можно найти здесь: https://gist.github.com/omnilord/4f308d4a1d0b9df02293dcaa8ee4d605.

Передняя Материя

В Rails 5.2 есть очень полезная функция ActiveStorage. ActiveStorage позволяет очень просто прикреплять файлы к моделям, не добавляя лишнего мусора или других драгоценных камней, чтобы подкорректировать каркас. Сложно найти примеры того, как делать некоторые полезные вещи, поэтому я решил заполнить пробелы на некоторое время, пока не появится лучший сигнал, чтобы избавиться от шума.

*** Все пути, представленные здесь, относятся к корневому каталогу rails, если не указано иное. ***

Настраивать

Начнем с того, что здесь нет никаких драгоценных камней, потому что ActiveStorage встроен прямо в Rails начиная с версии 5.2.0. Это упрощает начало работы очень. Поскольку он уже существует, четыре основных момента, которые вам нужно знать с точки зрения кода, являются обычными подозреваемыми: миграция, модель, контроллер и представление.

Ладно, я немного пообдал, вам может понадобиться один или два драгоценных камня, чтобы все работало так, как я описываю здесь, но их включение открывает потрясающую функциональность с очень небольшой настройкой. Если вы планируете выполнять преобразования (изменение размера, кадрирование и т. Д.), Вам понадобится интерфейс ImageMagick. mini_magick - хороший выбор из моего ограниченного опыта. И вам понадобится SDK для выбранного вами облачного хранилища. В этом примере я использую AWS S3. Вам следует добавить эти две строки в свой Gemfile, если вы планируете использовать любую из этих функций:

gem ‘aws-sdk-s3’ #swap this out for Azure or GCP as needed
gem ‘mini_magick’

Хорошо, на хранение…

Сначала запустите генератор ActiveStorage и выполните миграцию rails active_storage:install && rails db:migrate, чтобы настроить таблицы.

Во-вторых, настройте модели, добавив has_one_attached или has_many_attached к моделям, к которым вы хотите прикрепить одно или несколько изображений соответственно. Оба макроса принимают в качестве аргумента один символ - имя поля для вложения. Поле has_many_attached будет массивом вложений, так что помните об этой детали, если вы ее используете.

В-третьих, вам нужно добавить имя (а) поля в разрешение params в контроллере для целей очистки (вы используете строгие параметры, не так ли?). Кроме того, любая информация о выборе эскизов также должна быть разрешена и очищена по мере необходимости. В своем приложении я использую расширение jQuery под названием Jcrop, чтобы выбрать квадрат для миниатюры, а затем передать эти значения обратно с помощью формы submit.

Наконец, представление - это то место, где вы будете проводить большую часть своего времени, потому что здесь нужно написать большинство строк кода. В простейшей реализации, в зависимости от вашего процесса, вы либо добавляете ActiveStorage в конвейер активов, либо включаете этап сборки ActiveStorage, если вы используете что-то вроде WebPacker. Я предпочитаю конвейер активов.

Наиболее существенную часть представления можно подытожить с помощью этого однострочного фрагмента:

<%= form.file_field(:fieldname, multiple: false, direct_upload: true) %>

Двумя важными особенностями здесь являются атрибуты multiple и direct_upload. Mutliple координируется с ActiveStorage, чтобы упростить загрузку нескольких изображений в виде группы для has_many_attached полей, но это скучно по сравнению с тем, что делает direct_upload: true. В следующем разделе мы обсудим облачное хранилище и прямую загрузку.

Облачное хранилище

По умолчанию ActiveStorage сохраняет данные в каталоге /storage на локальном хосте и не требует дополнительной работы, но во многих производственных системах базовый хост является временным и может быть динамически завершен по мере необходимости различными процессами DevOps, что делает локальное хранилище непригодной конфигурацией.

Настройка ActiveStorage для облака обманчиво проста - обманчиво, потому что кажется, что это должен быть сложный процесс, а раньше так и было, поскольку требовалось включение дополнительных драгоценных камней, которые поставляются с собственным набором шаблонного кода; просто, потому что ActiveStorage обеспечивает очень небольшую площадь поверхности и практически не имеет шаблонов (кроме миграции) для выполнения всего, что вам нужно, все прямо под капотом и стандартно. Пока вы предоставляете своему облачному провайдеру учетные данные для доступа к ресурсу хранилища (например, используете IAM для настройки учетных данных и настраиваете общедоступную корзину S3 с этими учетными данными, чтобы разрешить загрузку), вы - золотой.

При установке Rails 5.2 просмотр /config/storage.yml показывает все конфигурации, необходимые для настройки выбранного вами облачного хранилища (GCS, AWS S3, AzureStorage и т. Д., Вы даже можете настроить несколько целей с помощью параметра зеркала). Вы заметите, что извлекаются некоторые учетные данные *, что является отличной функцией безопасности.

После сохранения учетных данных установите для config.active_storage.service значение желаемого символа поставщика услуг из /config/storage.yml в файле требуемой среды в /config/environments/, и ActiveStorage сохранит файлы с этим поставщиком. Не забудьте перезагрузить сервер, если вы измените конфигурацию во время его работы.

В предыдущем разделе мы представили фрагмент, содержащий элемент представления для загрузки файлов, и он содержит атрибут с именем direct_upload. При обычных операциях файл будет загружен в ваше приложение Rails, а затем через гем облачного хранилища в конечный пункт назначения. Используя прямую загрузку, форма в браузере фактически отправит файл непосредственно в облачное хранилище во время процесса отправки с использованием подписанного URL-адреса. Есть даже обратные вызовы JavaScript, которые вы можете прикрепить к форме для обработки событий (начало, конец, прогресс и т. Д.) Во время жизненного цикла загрузки. Это значительно снизит полосу пропускания, за которую отвечает ваше приложение, с очень небольшим компромиссом.

Что касается безопасности, имейте в виду, что использование direct_upload действительно имеет некоторые известные лазейки при доступе к файлам через подписанные URL-адреса. Существует некоторая дискуссия о том, как, если вы получите один подписанный URL-адрес, вы можете использовать его для доступа к другим файлам, но, похоже, это крайний случай, о котором большинству разработчиков не нужно будет беспокоиться. Не стесняйтесь просматривать базу данных проблем Rails на Github, чтобы найти дополнительную информацию по этой проблеме.

* Если вы не знаете, что такое хранилище учетных данных, на Engine Yard есть красивая статья об этой функции.

Локальное дисковое хранилище

Если вы посмотрите /config/storage.yml, вы увидите, что там есть раздел верхнего уровня под названием local. Это для хранения файлов в локальной системе. Это должно быть хорошо, если вы не используете SSL локально в Rails 5.2 RC1, возможно, как конфигурацию Puma. Я потратил несколько дней, пытаясь отладить эту проблему, и в конце концов отправил ее в Rails и получил хороший ответ от Джорджа Клагхорна. Существует недокументированный, устаревший параметр, который вы можете использовать, чтобы указать ActiveStorage, какой хост использовать для URL-адреса хранилища. Добавьте host: https://localhost:3000 в локальную настройку, и URL-адреса ваших файлов должны отображаться правильно. Этот параметр устарел и будет удален в RC2 в пользу автоматического определения URL-адреса хоста, но если вы похожи на меня и работаете над 5.2 раньше, это ценная информация.

Проверка (или ее отсутствие)

Одна важная вещь из соображений безопасности: по состоянию на 1 марта 2018 г. НЕ ПОДДЕРЖИВАЕТСЯ ПОДДЕРЖКА ПРОВЕРКИ ФАЙЛОВЫХ BLOB-ДАННЫХ. В этой проблеме Джордж Клагхорн заявляет, что большие двоичные объекты будут загружены независимо от размера или типа файла или любой другой проверки, выполняемой на связанном ресурсе. Насколько я могу судить, вам нужно будет вручную очистить вложение (возможно, в асинхронном задании), если вы не пройдете проверку. Я не проводил никакого личного тестирования этого, но я буду обновлять, когда мне нужно будет решить эту ситуацию в моем приложении.

Трансформация (также известные как варианты)

Еще одна вещь, которая сильно сбивала с толку, заключалась в том, как работает вариантная функциональность. Откровенно говоря, это просто карта базовой функциональности ImageMagick, но то, какие варианты доступны, не совсем легко отследить. В документации Rails показано только изменение размера, чего было недостаточно для моего решения для эскизов аватара.

Для этой функции мне нужно, чтобы конечные пользователи выбирали квадрат для обрезки нужного эскиза. Эта часть проста (с использованием Jcrop), но как функция Variant вписалась бы в мой рабочий процесс, теперь ЭТО был приемник времени, который занимал слишком много времени, насколько это просто.

Во-первых, вы можете указать несколько инструкций по преобразованию. Просто передайте несколько пар ключ / значение в том порядке, в котором вы хотите, чтобы они выполнялись (до сих пор у меня не было проблем с доступом к хешу вне порядка). Чтобы показать вам, что я сделал, в моей модели Profile у меня есть следующий метод, который возвращает вариант эскиза:

def thumbnail(size = ‘100x100’)
  if avatar.attached?
# I store thumbnail meta-information as a json field
    # named `thumb_settings` in PostgreSQL.
    # YMMV depending on your persistence layer options.
if thumb_settings.is_a? Hash
dimensions = “#{thumb_settings[‘h’]}x#{thumb_settings[‘w’]}”
      coord = “#{thumb_settings[‘x’]}+#{thumb_settings[‘y’]}”
avatar.variant(
        crop: “#{dimensions}+#{coord}”,
        resize: size
      ).processed
else
avatar.variant(resize: size).processed
end
  end
end

И вуаля, у нас есть доступ к миниатюре 100x100 (по умолчанию), обрезанной из исходного изображения, доступной при вызове метода. Что касается вариантов, здесь есть некоторая серьезная скрытая логика, возможно, некоторые узкие места, если производительность является серьезной проблемой, но по большей части вы можете рассматривать вариант, как и любое другое вложение.

То, что происходит за кулисами, когда вы вызываете обработанный вариант, очень хитро, очень умно, но очень хорошо сделано. Независимо от того, храните ли вы файлы локально или в облаке, создается вариантный каталог (если он еще не существует) и внутри подкаталога для конкретного вложения (подумайте, /storage/{2 letter token}/{2 letter token}/{image_hash}/, два подкаталога двухбуквенных токенов появляются только с локальный дисковый сервис). Этот внутренний каталог будет содержать файлы больших двоичных объектов для всех вариантов одной и той же загрузки. Это упрощает поиск всех вариантов одного и того же файла в одном месте.

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

И да, когда вы удаляете исходное вложение или заменяете его новым изображением, ВСЕ варианты также удаляются.

Как насчет этого? Магия, которая работает!

Что касается производительности, если вы выполняете некоторые сложные преобразования, которые могут потребовать большого объема ресурсов, вы, вероятно, захотите сделать что-то асинхронное, например, явно выполнить processed в фоновом процессе ActiveJob. Если вас это не волнует, вы можете полностью опустить вызов метода «loaded» в своем коде, и первая попытка загрузить вариантный URL неявно создаст большой двоичный объект. Каким бы способом вы это не написали, изображение в конечном итоге будет сгенерировано, и это будет сделано с минимальными усилиями.

Еще одна мысль о преобразованиях: если вы используете прямую загрузку, ваше приложение будет использовать полосу пропускания для загрузки исходного изображения из облачного провайдера, а затем по завершении загрузки варианта. Я не знаю (и, вероятно, должен изучить), можете ли вы перехватить файл во время процесса сохранения, чтобы выполнить преобразования перед его отправкой в ​​конечный пункт назначения. Учитывая, что обратных вызовов проверки нет, я пока не ожидаю, что это возможно, но я могу ошибаться и просто не нашел правильный код для выполнения трюков.

Внутреннее приложение

Одна вещь, которая пока не очевидна, - это как прикреплять файлы вручную из приложения, а не из загруженного файла.

Одна из первых попыток, которые я предпринял с эскизами, прежде чем выяснить, что Варианты автоматически делают все, что мне нужно, была в чрезмерно упрощенном коде:

class Profile < ApplicationRecord
  has_one_attached :avatar
  has_one_attached :thumbnail
  after_save :set_thumbnail
…
private
def set_thumbnail
    if avatar.attached?
      thumbnail.attach(avatar.variant(crop: …, resize: …))
    end
  end
end

Этот подход не только неэффективен, но и не позволяет правильно использовать ActiveStorage; эта функция построена немного умнее, чем эта. Что ActiveStorage делает, когда создает (и сохраняет) вариант, так это хеширует и создает уникальный ключ для каждого варианта и сохраняет те, что каждый последующий вызов варианта будет захватывать ранее сохраненное изображение! Уууу! С ума сойти! И если вы очистите исходный файл, все варианты также будут неявно удалены. Я повторяю это, потому что когда-то меня беспокоило наличие всех этих дополнительных файлов. Это отличная сборка мусора и та архитектура, которую я уважаю, люблю и с радостью работаю - программное обеспечение от Basecamp - потрясающая штука.

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

avatar.attach(io: File.open(“/path/to/face.jpg”),
              filename: “face.jpg”,
              content_type: “image/jpg”)

Если я правильно помню, вы можете использовать параметр `io` для вариантов в памяти, но так как я не заставил его работать с моей функцией эскизов, не цитируйте меня по этому поводу.

Удаление

Удалить относительно просто. Документация проста. Если вы можете удалить немедленно, запустите purge для вложения. Если вам нужно переключить его в фоновый процесс из соображений производительности, используйте purge_later.

Что касается работы из внешнего интерфейса, просто используйте обычный способ Rails. Используйте флажок или скрытое поле, которое возвращается вместе с формой и выполняет удаление, если поле «удалить» положительное.

Запоздалые мысли

После всего этого изучения ActiveStorage, возможно, стоит выделить одну вещь: отсутствие именованных вариантов. Это будет возможность пометить вариант как «эскиз» или «баннер» или любой другой ярлык, который вы хотите применить к нему, и когда конечный пользователь сохраняет модель, этот именованный вариант заменяется в файловом хранилище, а не просто добавлен в микс. Прямо сейчас, с учетом того, как написан мой аватар в моем профиле, если конечный пользователь сядет и переместит кадрирование, сохранит, переместит его снова, сохранит и т. Д., Появятся многочисленные дополнительные файлы вариантов, которые никогда не загрузись снова. Я не нашел никаких методов для чистого получения существующих вариантов или их удаления, кроме ручного отслеживания файловой структуры (что становится трудным, когда файлы не хранятся локально).

А пока желаю удачного кодирования.

Отказ от ответственности

Обычно я не публикую статьи о коде, потому что не хочу создавать шума там, где другие являются гораздо лучшими авторами или где есть много существующих исходных материалов. Однако в случае с ActiveStorage было так мало полезной информации об этой удивительной функции Rails, что я делюсь этими выводами и делаю это сейчас, даже когда Rails все еще находится в RC1 (на момент написания этой статьи). Это краткое изложение полезной информации, а не полное руководство. Прошу прощения за любые недостатки в стиле письма.