Shrine upload from script using Uploader

Hello,

we have successfully integrated Shrine uploads to S3, it is amazing software. But I need to upload bunch of old images over our uploader used in model by script. Probably rake task.

I don’t know how :(. I have ImageUploader class with derivatives and validations. And I need to do something like this:

image = image.first # path to image to upload

Images.new(
  title: title,
  image: image # here I need to pass image to the uploader used in model

But I don’t know how. I tried pass open file, path, create instance of ActionDispatch::Http:UploadedFile etc.

Please help, I think it is simple but I miss it.

Thanks!

It’s not clear to me what the issue is. At which part are you stuck? What exactly doesn’t work?

I have included Shrine into my project according to getting started manual. With more advanced uploader and S3. And I want to upload images in rake task, I have only paths to files, and I want to attach the file. When I create model instance, what I should pass to image when I want to image be uploaded, resized and all info is stored to image_data like this:

{"id":"productimage/66/image/a57d012152a326842b0b04863cde1a89.jpg","storage":"store","metadata":{"filename":"product-image.jpg","size":141103,"mime_type":"image/jpeg"},"derivatives":{"xs":{"id":"productimage/66/image/xs-a155e162e04cdd147fc79232be900bc1.jpg","storage":"store","metadata":{"filename":"image_processing20210524-10868-zglie7.jpg","size":35410,"mime_type":null}},"sm":{"id":"productimage/66/image/sm-f544cbbf817afb0bb8ff96667cc357d5.jpg","storage":"store","metadata":{"filename":"image_processing20210524-10868-fj33w6.jpg","size":69122,"mime_type":null}},"smd":{"id":"productimage/66/image/smd-8f269874664c5c8c1632242d53cb17e3.jpg","storage":"store","metadata":{"filename":"image_processing20210524-10868-1r4ngnu.jpg","size":117961,"mime_type":null}},"md":{"id":"productimage/66/image/md-8844af10c9ac647717b014a7b8ebc53e.jpg","storage":"store","metadata":{"filename":"image_processing20210524-10868-1n09lyj.jpg","size":141073,"mime_type":null}},"lmd":{"id":"productimage/66/image/lmd-f8fa46ac6a733e40fb0344dfb9a09038.jpg","storage":"store","metadata":{"filename":"image_processing20210524-10868-1aphg6g.jpg","size":141073,"mime_type":null}},"slg":{"id":"productimage/66/image/slg-23ace373a45663a1e18658f86b054bdc.jpg","storage":"store","metadata":{"filename":"image_processing20210524-10868-1i1vteg.jpg","size":141073,"mime_type":null}},"lg":{"id":"productimage/66/image/lg-104174315a8188bad33af16d5cee1024.jpg","storage":"store","metadata":{"filename":"image_processing20210524-10868-qajoi.jpg","size":141073,"mime_type":null}},"llg":{"id":"productimage/66/image/llg-27e9541284d1fc7e8114616423b66d1b.jpg","storage":"store","metadata":{"filename":"image_processing20210524-10868-16wnvr7.jpg","size":141073,"mime_type":null}},"webp_xs":{"id":"productimage/66/image/webp_xs-be6362be57d9949a5ad6193030980830.webp","storage":"store","metadata":{"filename":"image_processing20210524-10868-gpl11q.webp","size":21442,"mime_type":null}},"webp_sm":{"id":"productimage/66/image/webp_sm-67b81b8141de63e91492dc7d96ca7e9f.webp","storage":"store","metadata":{"filename":"image_processing20210524-10868-bly3pa.webp","size":37994,"mime_type":null}},"webp_smd":{"id":"productimage/66/image/webp_smd-fe4909fa22fec12c556380ff2f9e7a1b.webp","storage":"store","metadata":{"filename":"image_processing20210524-10868-1ugdwk5.webp","size":63502,"mime_type":null}},"webp_md":{"id":"productimage/66/image/webp_md-af9bfd304844f288b9e9270275beacf3.webp","storage":"store","metadata":{"filename":"image_processing20210524-10868-1346fcq.webp","size":77786,"mime_type":null}},"webp_lmd":{"id":"productimage/66/image/webp_lmd-387e9e4bac5417ec1b4f313c950c1098.webp","storage":"store","metadata":{"filename":"image_processing20210524-10868-1rtgn5n.webp","size":77786,"mime_type":null}},"webp_slg":{"id":"productimage/66/image/webp_slg-cdcaecfda000516a572fdb4c94aae1ce.webp","storage":"store","metadata":{"filename":"image_processing20210524-10868-134pz1k.webp","size":77786,"mime_type":null}},"webp_lg":{"id":"productimage/66/image/webp_lg-fe69f2b7e84fdc7e9657ac48a97a1201.webp","storage":"store","metadata":{"filename":"image_processing20210524-10868-1egzi88.webp","size":77786,"mime_type":null}},"webp_llg":{"id":"productimage/66/image/webp_llg-8827fc65145458b73a504a70d3ee11e8.webp","storage":"store","metadata":{"filename":"image_processing20210524-10868-f2fiv4.webp","size":77786,"mime_type":null}}}}

I read filenames from filesystem and then I want to create db records and upload them. But when I pass it to the model instance like this:

Images.new(
  title: title,
  image: image # here I need to pass image to the uploader used in model
).save

And inside image is path to image, it does not work. When I use form it passes with params instance of ActionDispatch::Http:UploadedFile , and it works.

How can I upload image using uploader just in rake task please? If I just pass path it fails on

(byebug) new_image.image = image
*** JSON::ParserError Exception: 784: unexpected token at 'www/pictures/553/original/product-image.png'

nil

I need to create uploads like in my form, but programatically in the loop of rake task.

My uploader is

require "image_processing/mini_magick"

class ProductImageUploader < Shrine

  Attacher.validate do
    validate_size      1..5*1024*1024
    validate_mime_type %w[image/jpeg image/png image/webp image/tiff]
    validate_extension %w[jpg jpeg png webp tiff tif]
  end

  Attacher.derivatives do |original|
    image_dimensions = Config::PRODUCT_IMAGE_DIMENSIONS
    magick = ImageProcessing::MiniMagick.source(original)
    {
      # original image
      xs: magick.resize_to_limit!(image_dimensions[:xs][:width], image_dimensions[:xs][:height]),
      sm:  magick.resize_to_limit!(image_dimensions[:sm][:width], image_dimensions[:sm][:height]),
      smd:  magick.resize_to_limit!(image_dimensions[:smd][:width], image_dimensions[:smd][:height]),
      md:  magick.resize_to_limit!(image_dimensions[:md][:width], image_dimensions[:md][:height]),
      lmd:  magick.resize_to_limit!(image_dimensions[:lmd][:width], image_dimensions[:lmd][:height]),
      slg:  magick.resize_to_limit!(image_dimensions[:slg][:width], image_dimensions[:slg][:height]),
      lg: magick.resize_to_limit!(image_dimensions[:lg][:width], image_dimensions[:lg][:height]),
      llg:  magick.resize_to_limit!(image_dimensions[:llg][:width], image_dimensions[:llg][:height]),
      # webp image conversion
      webp_xs: magick.convert("webp").resize_to_limit!(image_dimensions[:xs][:width], image_dimensions[:xs][:height]),
      webp_sm: magick.convert("webp").resize_to_limit!(image_dimensions[:sm][:width], image_dimensions[:sm][:height]),
      webp_smd: magick.convert("webp").resize_to_limit!(image_dimensions[:smd][:width], image_dimensions[:smd][:height]),
      webp_md: magick.convert("webp").resize_to_limit!(image_dimensions[:md][:width], image_dimensions[:md][:height]),
      webp_lmd: magick.convert("webp").resize_to_limit!(image_dimensions[:lmd][:width], image_dimensions[:lmd][:height]),
      webp_slg: magick.convert("webp").resize_to_limit!(image_dimensions[:slg][:width], image_dimensions[:slg][:height]),
      webp_lg: magick.convert("webp").resize_to_limit!(image_dimensions[:lg][:width], image_dimensions[:lg][:height]),
      webp_llg: magick.convert("webp").resize_to_limit!(image_dimensions[:llg][:width], image_dimensions[:llg][:height])
    }
  end

end

And my model is

class ProductImage < ActiveRecord::Base
  belongs_to :product

  include ProductImageUploader::Attachment(:image)

  validates_presence_of :image, message: "Image must be included."

end

In my controller, this works:

  def create
    product_image = ProductImage.new(
      product_id: params[:product_image][:product_id].to_i,
      label: params[:product_image][:label],
      locale_data: locale_data_json,
      image: params[:product_image][:image]
    )
    if product_image.save
...

But in params[:product_image][:image] there is instance of ActionDispatch::Http:UploadedFile . But how can I pass image file from disk, please?

Should I create instance of any object from image path or so please? Maybe Like here, from your blog?

uploader = ImageUploader.new(:disk)
uploader.upload(image) #=> #<Shrine::UploadedFile>

How can I create instance of Shrine::UploadedFile for example from image path from drive?

Aha, you need to pass a File object instead of a path. Shrine needs an IO-like object to upload. This is documented here.

Ah, I see in doc there is necessary to open with binary mode. I tried simply just File.open(image) but not in binary mode, it worked but without mime type so my validation failed. I am going to try this and write the result here for the others.

Thanks so much for help and also for this great gem!

It works like charm, I missed the binary mode. So that it is:

        image: File.open(image, binmode: true)

and image is uploaded perfectly

Again thanks to Janko for a great job