0

I am switching from Paperclip to Shrine because of Paperclip deprecation.

In my model Profilepic.rb file I used to retrieve dimensions of an image variant as follow:

before_create  :save_ratio    

def save_ratio
    geo = Paperclip::Geometry.from_file(image.queued_for_write[:original])
    self.ratio = geo.width / geo.height
  end

Basically I am saving the vertical ratio of an image.

It was working well with Paperclip : I grabbed the temporary image queued_for_write and checked dimensions with Paperclip::Geometry before saving the value.

In Shrine I have added the following to the uploader :

plugin :add_metadata
plugin :store_dimensions

It works great as I have all information available and images are uploaded to S3 properly.

Yet my new method for saving the image ratio no longer works :

after_create  :save_ratio
def save_ratio
    self.ratio = self.image[:original].width.to_i / self.image[:original].height.to_i
  end

I get

error undefined method `[]' for ProfilepicUploader::UploadedFile:0x00007f69a685c750>

Whereas, in console, after image has been created :

Profilepic.first.image[:original].width.to_i

does return the correct value.

EDIT

My uploader :

require "image_processing/mini_magick"

class ProfilepicUploader < Shrine
    include ImageProcessing::MiniMagick
    plugin :processing
    plugin :validation_helpers # to validate image data
    plugin :versions
    plugin :add_metadata
    plugin :store_dimensions

    Attacher.validate do
        validate_max_size 5.megabyte
        validate_mime_type_inclusion ['image/jpg', 'image/jpeg', 'image/png']
    end

    process(:store) do |io, context|
        versions = { original: io } # retain original

        io.download do |original|
            pipeline = ImageProcessing::MiniMagick.source(original)

            versions[:editable]  = pipeline.resize_to_fit!(700, 700)
            versions[:thumbnail] = pipeline.resize_to_fill!(400, 400)
            versions[:small]  = pipeline.resize_to_fill!(200, 200)
        end

        versions # return the hash of processed files
    end

end

my model :

class Profilepic < ApplicationRecord
  require "image_processing/mini_magick"
  belongs_to :professionnel
  before_create :set_hashid

  include ProfilepicUploader::Attachment.new(:image) # adds an `image` virtual attribute
  include ProfilepicFinalUploader::Attachment.new(:final_image) # adds an `image` virtual attribute


 attr_accessor :crop_x, :crop_y, :crop_w, :crop_h   

 after_create  :save_ratio_image   

private

  def save_ratio
    self.ratio = self.image[:original].width.to_i / self.image[:original].height.to_i
  end

end
Maxence
  • 2,029
  • 4
  • 18
  • 37

2 Answers2

2

Shrine first saves the record with the cached file, then again with the stored processed files. The reason you're getting the error is because your code is first being run when cached file is being assigned, at which point no processing has yet been done, and self.image is a single uploaded file (not a Hash of uploaded files).

What you probably want is extract the ratio already from the cached file (which is the same uploaded file as the :original after processing):

def save_ratio
  if image_data_changed? && image_attacher.cached?
    self.ratio = image.width.to_f / image.height.to_f
  end
end

Alternatively, you can create image_width and image_height columns and have Shrine automatically populate them with width & height metadata, then calculate the ration later in SQL.

class ProfilepicUploader < Shrine
  plugin :metadata_attributes, width: :width, height: height
end

As mentioned in the other answer's comments, you can also use a JSONB column for the attachment column, which allows you to fetch the width & height metadata with SQL as well.

Janko
  • 8,985
  • 7
  • 34
  • 51
  • Thanks a lot. Actually I did the calculation in the view, and not in the model as I wasn't sure what was available in the model when the record was created (Json was obviously not ready yet). The view can be accessed only when the image is ready... which fixes the pb. Also Andre answer to grab context in uploader is probably correct (I got wrong ratio because of `to_i` instead of `to_f`) but I prefer to keep uploaders data and model data in their respective scope.. – Maxence Feb 26 '19 at 08:48
  • Actually I have implemented your solution from the cached file. It works perfectly. thks – Maxence Mar 05 '19 at 17:05
1

my 2 cents:

If you are using Shrine::Attach.promote to process files in background, through a job or worker and the versions metadata may will not be available at that time (after_create callback time).

I'm not sure what's the best way to solve this, but you may access the model in processing phase through the context (https://github.com/shrinerb/shrine/blob/v2.16.0/doc/processing.md#processing-on-upload):

class SomeUploader < Shrine
  process(:store) do |io, context|
    ratio = io.metadata["width"] / io.metadata["height"]
    model = context[:record]
    model.update(ratio: ratio)

    versions = { original: io } # retain original
    io.download do |original|
      pipeline = ImageProcessing::MiniMagick.source(original)
      versions[:small]  = pipeline.resize_to_limit!(300, 300)
      # ....
    end
    versions
  end
end
AndreDurao
  • 5,600
  • 7
  • 41
  • 61
  • Thanks I will try your solution. At the moment I try to implement this is the model. Maybe doing it from the uploader will work. It is hard to understand the scope of Uploader vs actual model with Shrine. I will try to find a resource going through the actual process and path of the image across the rails app, S3, the uploader .. – Maxence Feb 25 '19 at 18:54
  • Actually your link quit explains this. I will try to go thruugh it in depth. – Maxence Feb 25 '19 at 18:59
  • You can still access the width and height in that callback through the image_data attribute – AndreDurao Feb 25 '19 at 19:15
  • I just tried to do a Json.parse : `JSON.parse(Profilepic.first.image_data)["original"]["metadata"]["width"].to_i` does work in the console after the first profilepic has been created. Though `self.ratio = JSON.parse(self.image_data)["original"]["metadata"]["width"].to_i / JSON.parse(self.image_data)["original"]["metadata"]["height"].to_i` sends the same error. Like nothing can be accessed `after_create` in the model .. :/ I must be doing something wrong – Maxence Feb 25 '19 at 19:34
  • Your solution of updating the ration from the uploader returns no error, yet the field is always populated with `0`. But I am not doing the upload and processing async .. very strange. I will go on working on this .. – Maxence Feb 25 '19 at 19:44
  • 1
    I never needed to parse the JSON contents, I don't know about your model but if created a JSON / JSONB column (In postgreSQL) the hash is already serialized in your model; Remember that (1/2 ==0) in ruby; you may need to convert to a float to fit your needs – AndreDurao Feb 25 '19 at 19:54
  • Oh ok, actually I made it a text column as per Shrine quickstart guide https://github.com/shrinerb/shrine . I will make it a JSON column though. Regarding the ratio column, I have not touched it, it is a double precision and used to work fine with Paperclip. Also I have left Paperclip columns: `image_content_type` `image_file_size` though it should not really interfere .. I will delete them though, just to make sure it is not the pb – Maxence Feb 25 '19 at 20:22
  • 1
    FYI, Shrine returns integers for width & height, so you need to convert them to a float before division. – Janko Feb 26 '19 at 08:22