
Краткий этот туториал будет полезен, хотелось бы надеяться, как программистам, работающим с Ruby on Rails, так и тем из «племени младого незнакомого» веб-разработчиков, кто захочет освоить отличный инструмент, увидевший свет в Rails 5.2 («извлечен из прода» Basecamp 3 усилиями George Claghorn и Javan Makhmali) — фреймворк Active Storage. Фреймворк делает простым и на редкость удобным загрузку файлов в облако (прямо сразу «из коробки» доступны Amazon S3, Google Cloud Storage и Microsoft Azure Storage), также — если речь об изображениях, видео или PDF-файлах — создание превью на лету.
Не буду сейчас ни сравнивать Active Storage с задолго до него существующими решениями CarrierWave, Paperclip или Shrine, ни рассуждать о полиморфизме таблицы active_storage_attachments; все это хорошо и многажды описано, без проблем способно быть, при желании и наличии интереса, найдено в доках. Остановлюсь всего только на нескольких практических моментах использования класса ActiveStorage::Variant, требующих, как известно, ImageMagick или libvips посредством джема image_processing:
# Gemfile gem "image_processing", "~> 1.0"
Забегая вперед и чтобы не казаться голословным: живая демка, также ссылка на гитхаб здесь. «Тот, кто дойдет — увидит», так что вперед.
Генерация HTML галереи изображений не вызывает больших вопросов, более-менее в соответствии с докой итерируем массив и увязываем пути с классами (например) бутстрапа, создав для удобства простенький хелпер:
def path_to_file(x) Rails.application.routes.url_helpers.rails_blob_path(x, only_path: true) end
<% @slideshow.images.each do |x| %> <%= link_to( path_to_file(x), html_options = { "data-lightbox" => "photo", "class" => "col-sm-4" }) do %> <%= image_tag x.variant(@options), "class" => "img-fluid" %> <% end %> <% end %>
Лайтбокс — любой из несть им числа. Какой хотите, без разницы.
Панель управления галереи на основе Active Admin выглядит вполне пристойно:

, что реализовано следующим образом:
ActiveAdmin.register Slideshow do permit_params :published_at, :name, :options, images: [] remove_filter :images_attachments, :images_blobs, :options scope :all scope :published scope :unpublished action_item :publish, only: :show do link_to 'Publish', publish_admin_slideshow_path(slideshow), method: :put unless slideshow.published_at? end action_item :unpublish, only: :show do link_to 'Unpublish', unpublish_admin_slideshow_path(slideshow), method: :put if slideshow.published_at? end action_item :delete_images, only: :show do if slideshow.images.attached? link_to 'Delete Images', delete_images_admin_slideshow_path(slideshow), method: :delete end end member_action :publish, method: :put do slideshow = Slideshow.find(params[:id]) slideshow.update(published_at: Time.zone.now) redirect_to admin_slideshow_path(slideshow) end member_action :unpublish, method: :put do slideshow = Slideshow.find(params[:id]) slideshow.update(published_at: nil) redirect_to admin_slideshow_path(slideshow) end member_action :delete_images, method: :delete do slideshow = Slideshow.find(params[:id]) # asset = ActiveStorage::Attachment.find_by(params[:attachment_id]) slideshow.images.purge_later redirect_to admin_slideshow_path(slideshow) end member_action :delete_image, method: :delete do slideshow = Slideshow.find_by(params[:name]) slideshow.images[params[:id].to_i].purge_later redirect_to admin_slideshow_path(slideshow) end form do |f| f.inputs 'Slideshow' do f.input :name f.input :options, input_html: { value: f.object.options || '{ "resize_to_limit": [300, 222], "kuwahara": "3%", "quality": 15 }' }, label: 'Options. For example: { "resize_to_limit": [300, 222], "monochrome": true }' f.input :images, as: :file, input_html: { multiple: true } end f.actions end show do |t| attributes_table do if t.images.attached? t.images.each_with_index do |img, index| span do link_to delete_image_admin_slideshow_path(index), method: :delete do image_tag img.variant(resize_to_limit: [100, 100]) end end end end row :name row :created_at row :updated_at row :published_at end para 'Click the preview to delete the image.' end end
Но тут вдруг возникает вопрос: каким образом передать в ActiveStorage::Variant параметры обработки изображений? — несложно указать напрямую в коде, но для удобства работы администратора сайта хотелось бы (несмотря на любую контраргументацию) записать их в базу данных, обеспечив возможность ввода и редактирования из админки. Но не evalом же их потом доставать, в самом-то деле! — небезопасно, согласитесь. Даже с учетом рубиновых уровней безопасности (с которыми, к слову, вообще непонятно что происходит):
@options = proc { $SAFE = 1 eval(Slideshow.take.options) }.call
Нет, так не пойдет. Плавно двигаясь эмпирическим (а как еще) путем познания — приходим к тому, что параметры вполне можно записывать и хранить как JSON:
class AddOptionsToSlideshow < ActiveRecord::Migration[6.1] def change add_column :slideshows, :options, :json end end
Таким образом, совсем просто:
@options = Slideshow.take.options
Дальше — больше. Что мешает нам валидировать вводимый админом JSON, указав допустимые для тех или иных ключей (коих ImageMagick имеет великое множество, глаза разбегаются) типы значений? — ровно ничто не мешает. Инсталлируем:
gem 'activerecord_json_validator', '~> 2.0.0'
, добавляя в модель валидацию:
PROFILE_JSON_SCHEMA = Pathname.new(Rails.root.join('config', 'schemas', 'slideshow.json')) validates :options, presence: true, json: { schema: PROFILE_JSON_SCHEMA, message: ->(errors) { errors } }
, профайл же slideshow.json, на основе которого пойдет проверка, выглядит у нас, предположим (полностью зависит от чувства меры и художественного вкуса разработчика), следующим образом:
{ "type": "object", "$schema": "http://json-schema.org/draft-04/schema#", "properties": { "resize_to_limit": { "type": "array" }, "monochrome": { "type": "boolean" }, "kuwahara": {"type": "string"}, "sepia-tone": {"type": "string"}, "quality": { "type": "integer" }, "polaroid": { "type": "integer" } }, "required": ["resize_to_limit"] }
Все уже давно работает, но ведь на рельсах аппетит приходит во время езды. Хм, а какой русский не любит быстрой езды… ок, пишем спек:
require 'rails_helper' RSpec.describe Slideshow, type: :model do it { is_expected.to have_many_attached(:images) } describe 'validates data column' do subject { described_class.new(options: options) } let(:valid_data) do { "resize_to_limit": [1, 2], "kuwahara": 'string', "polaroid": 1 } end describe 'valid data' do let(:options) { valid_data } it { is_expected.to be_valid } end describe 'value type is invalid (case 1)' do let(:options) { valid_data.merge quality: 'string' } it { is_expected.not_to be_valid } end describe 'value type is invalid (case 2)' do let(:options) { valid_data.merge monochrome: 'array' } it { is_expected.not_to be_valid } end describe 'value type is valid' do let(:options) { valid_data.merge monochrome: true } it { is_expected.to be_valid } end describe 'missing a optional element' do let(:options) { valid_data.except :kuwahara } it { is_expected.to be_valid } end describe 'missing a necessary element' do let(:options) { valid_data.except :resize_to_limit } it { is_expected.not_to be_valid } end end end
Всего семь тестов: убеждаемся, что данные :valid_data адекватны, затем добавляем к ним один за другим пару элементов, где value принадлежат типу данных, противоречащих указанным в профайле, и еще один, где значение соответствует нужному; напоследок удаляем необязательный элемент и, наконец, обязательный. Все тесты должны пройти:
$ bundle exec rspec spec/models/slideshow_spec.rb ....... Finished in 0.33026 seconds (files took 10.42 seconds to load) 7 examples, 0 failures
Finished. Cool. «If you ain’t drunk, then you’re in the wrong club.» (c)
ссылка на оригинал статьи https://habr.com/ru/post/651227/
Добавить комментарий