Keep original file after processing?

Hello, new to shrine here.

First want to thanks as it seems a really great gem. Congratulations!

I have some small issues though, I was trying to process a file reducing it’s dimensions if it’s greater than a value. My plan is change the original file, not store it as a “derivative”.

So after some reading found the processing plugin. In my uploader i have

class FileUploader < Shrine
  plugin :instrumentation
  plugin :determine_mime_type, analyzer: :marcel
  plugin :cached_attachment_data
  plugin :processing
  plugin :store_dimensions
  plugin :processing
  process(:store) do |io, context|
    io.download do |original|
      ImageProcessing::MiniMagick.source(original).resize_to_limit!(1920, 1920)
    end
  end
end

Is this the right thing to do? I notice two things with this configuration.

  1. The original file name of the file is lost. So instead of the image name uploaded (for example test.jpg) i end with something link image_processing20191026_xxxxx.jpg. If i don’t use the processing it stores the correct image.
    So is there any way to retain the original filename when using the processing plugin.

  2. Tried to use the process in a different action/phase (cache instead of store) but it seems not to be triggerd. What are the phases that the processing could be attached?

Last, one not so important question, is there any utility method to get what kind of “file” we are uploading? I mean something that for example could tell if we are uploading an image, so to only do the processing block if the uploaded file is an image, or triggering different block if it’s a video. I could develop one testing the mime types, asking just in case there is one already.

Thanks in advance

Hi, thank you for your kind words :slight_smile:

First of all, it’s highly recommended to keep the original file, as with processing you’re losing information. With the original you can always re-apply processing in the future if there is ever a need for it (e.g. you fixed a bug in your processing code).

That being said, you can replace the original explicitly without any plugin. Note that the processing plugin has been deprecated in 3.0, it’s not recommended anymore.

You can process the original as follows (the example assumes a Photo model with an image attachment):

original   = photo.image
normalized = original.download do |file|
  ImageProcessing::MiniMagick
    .source(file)
    .resize_to_limit!(1920, 1920)
end

uploaded_file = photo.image_attacher.upload normalized,
  metadata: { "filename" => original["filename"] }, # keep original filename
  delete: true # delete temporary file

photo.image_attacher.set(uploaded_file) # overwrite original file
photo.save

original.delete # delete the original if you want

Last, one not so important question, is there any utility method to get what kind of “file” we are uploading?

Not at the moment. I’ve been meaning to create a plugin for that, but it might take a while before I find time :slight_smile:

Thanks for the quick answer!

I know it’s good to keep the original file, the resize was just as an example. It’s for particular use cases that i need to overwrite the original.

So where all that code you send to me should go if it’s not on the processing plugin? I forgot to mention before but i’m currently using Shrine with Activerecord, but having it in an activerecord callback doesn’t seem the right thing to do.
Seems to me having it in the processing block was helping to have the functionality self contained in the uploader and was somehow transparent for me when uploading through forms. Should i use the derivate plugin for that?

Sorry if it’s a dumb question, just trying to use in the correct way.

Once again thank you for the speedy answer and for always giving feedback for issues and questions. :slight_smile:

You can put the code in a service object, e.g:

class ProcessOriginal
  def call(attacher)
    original  = attacher.file 
    processed = original.download do |file|
      # ...
    end

    uploaded_file = attacher.upload processed,
      metadata: { "filename" => original["filename"] },
      delete: true

    attacher.set(uploaded_file)
    attacher.persist # calls `record.save`

    original.delete
  end
end
# in your controller
photo = Photo.create(photo_params)
ProcessOriginal.new.call(photo.image_attacher)

# or in your background job
attacher = attacher_class.retrieve(...)
original = attacher.file
ProcessOriginal.new.call(attacher)
attacher.atomic_persist(original)

The reason I deprecated the processing plugin is because historically implicitness turned out to be problematic (that’s why the new derivatives plugin requires explicit triggering). There are many possible places where you might want to apply this processing, some of which might not be during any upload (and the process blocks are triggered only on an upload). You should still be able to trigger this processing in a DRY way without the processing plugin with service objects.