Possible bug: validations run after we've calculated metadata on original image

I’m not sure if it’s a bug or not, but sure seems like undesirable behavior.

Here’s my use-case:

  1. I’m uploading an image
  2. I generate a placeholder for the image and store it as metadata
  3. I unpack the placeholder to model using metadata_attributes

However, the metadata gets calculated before we’ve run the validations, which is critical in my case — it runs an exception if the data is not valid.

Here’s a script to reproduce it:

# frozen_string_literal: true

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'sequel'
  gem 'shrine'
  gem 'sqlite3'
  gem 'image_processing'
  gem 'mini_magick'

require 'sequel'
require 'mini_magick'
require 'image_processing'
require 'shrine'
require 'shrine/storage/memory'

Shrine.storages = {
  cache: Shrine::Storage::Memory.new,
  store: Shrine::Storage::Memory.new

Shrine.plugin :sequel

class MyUploader < Shrine
  plugin :add_metadata
  plugin :metadata_attributes, placeholder: :placeholder
  plugin :validation_helpers

  Attacher.validate do
    if validate_mime_type_inclusion(%w[image/jpg image/jpeg image/png])
      validate_max_width 20_000
      validate_max_height 20_000

  add_metadata :placeholder, skip_nil: true do |io, action:, **_context|
    placeholder = ::ImageProcessing::MiniMagick
                  .loader(page: 0)
                  .resize_to_fill(4, 4)

File.write('image.html', <<~HTML)
  <!doctype html />

DB = Sequel.sqlite # SQLite memory database
DB.create_table :posts do
  primary_key :id
  String :image_data
  String :image_placeholder

class Post < Sequel::Model
  include MyUploader::Attachment(:image)

post = Post.create(image: File.open('image.html'))

It’ll fail because HTML is not an image. I’ve tried changing order of the plugins to no avail.

There’s another problem: I’m using derivatives plugin, which won’t let me compute metadata for original unless action is cache. Which runs before validations.

If I compute the same metadata on a derivative, I won’t be able to use it with extract_metadata, as I understand from docs and my feeble attempts.

So here’s that.

Is it a bug that metadata gets calculated before validations? How do we solve it?

If I understand correctly, the problem is that the metadata extractor relies on the incoming file being an image. The thing is, metadata extraction needs to happen before validation, because validation is validating the extracted metadata. This order cannot be changed.

In your case, I would recommend extracting and saving the placeholder metadata outside of the Shrine lifecycle. You can use Attacher#add_metadata the add_metadata plugin provides.

class MyUploader < Shrine
  plugin :add_metadata
  plugin :metadata_attributes, placeholder: :placeholder
  plugin :validation_helpers

  Attacher.validate do ... end

  metadata_method :placeholder # if you want the file method

class ImagePlaceholder
  def self.call(file)
    file = ImageProcessing::MiniMagick
      .loader(page: 0)
      .resize_to_fill(4, 4)

    result = Base64.strict_encode64(file.read)



# ...

post = Post.new(image: image_file)

if post.valid?
  placeholder = post.image.download { |file| ImagePlaceholder.call(file) }
  post.image_attacher.add_metadata("placeholder" => placeholder)