Shrine with Rails несколько загрузок полиморфных изображений

Я боролся около 5 часов, пытаясь понять, почему Shrine блокирует мои загрузки. Я получаю либо ошибки типа "Shrine: Invalid file", либо "Ожидаемый массив, но есть строка" в сильных параметрах. Если ошибок нет, изображения на самом деле не сохраняются.

require "image_processing/mini_magick"

class ImageUploader < Shrine
  include ImageProcessing::MiniMagick

  plugin :activerecord
  plugin :backgrounding
  plugin :cached_attachment_data
  plugin :determine_mime_type
  plugin :delete_raw
  plugin :direct_upload
  plugin :logging, logger: Rails.logger
  plugin :processing
  plugin :remove_attachment
  plugin :store_dimensions
  plugin :validation_helpers
  plugin :versions

  Attacher.validate do
    validate_max_size 2.megabytes, message: 'is too large (max is 2 MB)'
    validate_mime_type_inclusion ['image/jpg', 'image/jpeg', 'image/png', 'image/gif']
  end

  def process(io, context)
    case context[:phase]
    when :store
      thumb = resize_to_limit!(io.download, 200, 200)
      { original: io, thumb: thumb }
    end
  end
end

class Image < ActiveRecord::Base
  include ImageUploader[:image]
  belongs_to :imageable, polymorphic: true
end

class Product < ApplicationRecord

  has_many :images, as: :imageable, dependent: :destroy    
  accepts_nested_attributes_for :images, allow_destroy: true
...

# Strong Params: 

def product_params
  params.require(:product).permit(
    :name, :brand_id, :category_id, :price, :compare_price, :description,
    images_attributes: { image: [] },
    product_properties_attributes: [:id, :property_id, :value]
  )
...

И мой взгляд:

  <%= f.fields_for :images do |image_form| %>
    <%= image_form.file_field :image, multiple: true %>
  <% end %>

Судя по всему, что я читал в документах или на горалах, это должно сработать. Нужно ли реструктурировать images_attributes хэш? Я также пытался использовать direct_uploads, но изо всех сил пытался заставить presigned_url работать с S3.

Refile делает это действительно простым делом, поэтому я, вероятно, снова заплачу.

Есть что-то, что я явно делаю не так?

1 ответ

Решение

Согласно fields_for документация, предоставленный блок будет вызываться для каждого изображения в project.images коллекция. Поэтому, если в вашем продукте в настоящее время нет изображений, блок не будет вызываться (согласно документации).

Чтобы вложенные атрибуты работали, вам нужно переслать следующие параметры при создании продукта:

product[images_attributes][0][image] = <file object or data hash>
product[images_attributes][1][image] = <file object or data hash>
product[images_attributes][2][image] = <file object or data hash>
...

Если вы посмотрите руководство Shrine " Несколько файлов", рекомендуется иметь только одно файловое поле, которое принимает несколько файлов:

<input type="file" name="file" multiple>

А затем настройте прямую загрузку для этого поля с помощью Uppy, динамически генерируя image поле для каждого загруженного файла, заполненного хэшем данных загруженного файла:

<input type="hidden" name="product[images_attributes][0][image]" value='{"id":"...","storage":"cache","metadata":{...}}'>
<input type="hidden" name="product[images_attributes][1][image]" value='{"id":"...","storage":"cache","metadata":{...}}'>
....

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

class ProductsController < ApplicationController
  def create
    images_attributes = params["files"].map { |file| {image: file} }
    Product.create(product_params.merge(images_attributes: images_attributes))
  end
end

В этом случае вы должны убедиться, что ваша HTML-форма имеет enctype="multipart/form-data" набор атрибутов (иначе будут представлены только имена файлов, а не сами файлы).

Другие вопросы по тегам