How to call `infer_extension` in custom plugin when defined as a dependency?

Hey there!

I couldn’t find anything regarding this change in the migration guide, but it seems that even by declaring the infer_extension plugin in load_dependencies I cannot call it. Or I am facing this change from the release notes somehow?

The private Shrine#infer_extension method has been removed.

However, I thought by defining it in the load_dependencies I would have access to it. I can call calculate_signature just fine.

The following (available as a gist) code works with Shrine 2 but not Shrine 3.

Shrine 3 raises an exception:

Traceback (most recent call last):
	20: from shrine-plugin-dependencies.rb:79:in `<main>'
	19: from gems/activerecord-6.1.4/lib/active_record/persistence.rb:37:in `create'
	18: from gems/activerecord-6.1.4/lib/active_record/inheritance.rb:72:in `new'
	17: from gems/activerecord-6.1.4/lib/active_record/inheritance.rb:72:in `new'
	16: from gems/activerecord-6.1.4/lib/active_record/core.rb:510:in `initialize'
	15: from gems/activemodel-6.1.4/lib/active_model/attribute_assignment.rb:34:in `assign_attributes'
	14: from gems/activerecord-6.1.4/lib/active_record/attribute_assignment.rb:13:in `_assign_attributes'
	13: from gems/activerecord-6.1.4/lib/active_record/attribute_assignment.rb:13:in `each'
	12: from gems/activerecord-6.1.4/lib/active_record/attribute_assignment.rb:21:in `block in _assign_attributes'
	11: from gems/activemodel-6.1.4/lib/active_model/attribute_assignment.rb:49:in `_assign_attribute'
	10: from gems/activemodel-6.1.4/lib/active_model/attribute_assignment.rb:49:in `public_send'
	 9: from gems/shrine-3.4.0/lib/shrine/plugins/model.rb:41:in `block in define_model_methods'
	 8: from gems/shrine-3.4.0/lib/shrine/plugins/model.rb:114:in `model_assign'
	 7: from gems/shrine-3.4.0/lib/shrine/attacher.rb:77:in `assign'
	 6: from gems/shrine-3.4.0/lib/shrine/attacher.rb:99:in `attach_cached'
	 5: from gems/shrine-3.4.0/lib/shrine/attacher.rb:117:in `attach'
	 4: from gems/shrine-3.4.0/lib/shrine/attacher.rb:182:in `upload'
	 3: from gems/shrine-3.4.0/lib/shrine.rb:108:in `upload'
	 2: from gems/shrine-3.4.0/lib/shrine.rb:208:in `upload'
	 1: from gems/shrine-3.4.0/lib/shrine.rb:300:in `get_location'
shrine-plugin-dependencies.rb:53:in `generate_location': undefined method `infer_extension' for #<MyUploader:0x00007fd382068fd0 @storage_key=:cache> (NoMethodError)

Code:

# frozen_string_literal: true

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem "activerecord"
  gem "sqlite3"
  ## Shrine v2
  # gem "shrine", "~> 2.19"
  # gem "shrine-memory"
  ## Shrine v3
  gem "shrine", "~> 3.4"
  gem "mime-types"
end

require "active_record"
require "shrine"
require "shrine/storage/memory"
require "down"

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

Shrine.plugin :activerecord
Shrine.plugin :determine_mime_type, analyzer: :mime_types

class Shrine
  module Plugins
    module HashLocation
      def self.load_dependencies(uploader, *)
        uploader.plugin :signature
        uploader.plugin :infer_extension
      end

      module InstanceMethods
        # Just an example, don't use in production!
        def generate_location(io, action:, name:, metadata:, **)
          if action == :upload
            super
          else
            # Works
            sha256 = calculate_signature(io, :sha256, format: :hex)
            p "SHA256: #{sha256}"

            first_dir  = sha256[0]
            second_dir = sha256[1]

            # Works in Shrine v2, but fails in v3 with:
            # shrine-plugin-dependencies.rb:52:in `generate_location': undefined method `infer_extension' for #<MyUploader:0x00007ffab4588f98 @storage_key=:cache> (NoMethodError)
            extension = infer_extension(metadata['mime_type'])
            p "Extension: #{extension}"

            filename = "#{name}-#{sha256}-#{extension}"

            [first_dir, second_dir, filename].join('/')
          end
        end
      end
    end

    register_plugin(:hash_location, HashLocation)
  end
end

class MyUploader < Shrine
  plugin :hash_location
end

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.connection.create_table(:posts) { |t| t.text :image_data }

class Post < ActiveRecord::Base
  include MyUploader::Attachment(:image)
end

Post.create(image: Down.download("https://loremflickr.com/cache/resized/65535_50735100323_e42ce59a8f_320_240_nofilter.jpg"))

Any help or explanation would be appreciated. Thanks!

Thank you, rubber duck debugging. Briefly after posting this I had another look and I was perhaps indeed facing the removal of this private method in 3.0.0, but I am not entirely sure.

The infer_extension plugin does not add to InstanceMethods but extends ClassMethods, so it can be called using:

self.class.infer_extension(metadata["mime_type"])

The difference to the calculate_signature plugin is, that it also adds a convenience method to InstanceMethods:

def calculate_signature(io, algorithm, format: :hex)
  self.class.calculate_signature(io, algorithm, format: format)
end