I've been struggling for about 5 hours trying to understand why Shrine is blocking my uploads. I either get errors like "Shrine: Invalid file", or "Expected Array but got string" in strong params. If there aren't errors, the images aren't actually saved.
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]
)
...
And my view:
<%= f.fields_for :images do |image_form| %>
<%= image_form.file_field :image, multiple: true %>
<% end %>
According to everything I've read on the docs or from gorails, this should work. Do I need to restructure the images_attributes
hash? I also tried using direct_uploads, but struggled to get the presigned_url to work with S3.
Refile makes this really easy, so I'll probably run crying back to that.
Is there something I'm obviously doing wrong?
According to the fields_for
documentation, the provided block will be called for each image in the project.images
collection. So if your product currently doesn't have any images, the block won't be called (according to the docs).
For nested attributes to work, you need to forward the following parameters when creating the Product:
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>
...
If you look at the "Multiple Files" Shrine guide, it's recommended that you just have a single file field which accepts multiple files:
<input type="file" name="file" multiple>
And then setup direct uploads for this field using Uppy, dynamically generating the image
field for each uploaded file populated with the uploaded file data hash:
<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":{...}}'>
....
Alternatively you can just let users attach multiple files, which are all submitted to the app, and then destructure them in the controller:
class ProductsController < ApplicationController
def create
images_attributes = params["files"].map { |file| {image: file} }
Product.create(product_params.merge(images_attributes: images_attributes))
end
end
In that case you have to make sure your HTML form has the enctype="multipart/form-data"
attribute set (otherwise only the files' filenames will get submitted, not files themselves).