—
title: Shrine 3.0.0¶ ↑
This guide covers all the changes in the 3.0.0 version of Shrine. If you’re currently using Shrine 2.x, see {Upgrading to Shrine 3.x}[https://shrinerb.com/docs/upgrading-to-3] for instructions on how to upgrade.
Major features¶ ↑
Derivatives¶ ↑
The new {derivatives}[https://shrinerb.com/docs/plugins/derivatives] plugin has been added for storing additional processed files alongside the main file.
Shrine.plugin :derivatives
class ImageUploader < Shrine Attacher.derivatives_processor do |original| magick = ImageProcessing::MiniMagick.source(original) { large: magick.resize_to_limit!(800, 800), medium: magick.resize_to_limit!(500, 500), small: magick.resize_to_limit!(300, 300), } end end
photo = Photo.new(photo_params) photo.image_derivatives! # creates derivatives photo.save
This is a rewrite of the {versions} plugin, bringing numerous improvements:
-
processed files are separated from the main file
“‘rb photo.image_data #=> # { # “id”: “original.jpg”, # “storage”: “store”, # “metadata”: { … }, # “derivatives”: { # “large”: { “id”: “large.jpg”, “storage”: “store”, “metadata”: { … } }, # “medium”: { “id”: “medium.jpg”, “storage”: “store”, “metadata”: { … } }, # “small”: { “id”: “small.jpg”, “storage”: “store”, “metadata”: { … } } # } # }
photo.image #=> processing is decoupled from promotion ability to add or remove processed files at any point “‘rb class ImageUploader < end # … sometime later … photo.image_derivatives!(:crop, left: 0, top: 0, width: 300, height: 300) photo.image_derivatives #=> { large: …, medium: …, small: …, cropped: … } photo.save “‘ possibility of uploading processed files to different storage “‘rb class ImageUploader < end photo.image_derivatives! photo.image_derivatives.storage_key #=> :other_store “‘ The { The Several more methods have been added: The new { The new { The new { The { There are several main differences compared to the old implementation: we are in charge of passing the record to the background job we can access the attacher before promotion we can react to errors that caused promotion to abort We can now also register backgrounding hooks on an attacher instance, allowing us to pass additional parameters to the background job: The persistence plugins ( The “atomic” methods use the new { The new { “‘rb Shrine.storages = { cache: …, store: …, backup: … } Shrine.plugin :mirroring, mirror: { store: :backup } The new { “‘rb Shrine.storages = { cache: …, cache_one: …, cache_two: …, store: … } Shrine.plugin :multi_cache, additional_cache: [:cache_one, :cache_two] The new { Model file assignment can now be configured to upload directly to permanent storage. New end “‘ The Including a The attachment data serializer is now configurable (by default “‘rb require “oj” # github.com/ohler55/oj Shrine.plugin :column, serializer: Oj “‘ It’s now possible to pass options to the validate block via the Validation can now be skipped on assignment by passing Closing the uploaded file can now be prevented by passing Uploaded file can now be automatically deleted by passing New New New The attached file is now parsed and loaded from record column only once, which can greatly improve performance if the same attached file is being accessed multiple times. The The The The memory storage from the shrine-memory gem has been merged into core. The “‘rb photo.image = { “id” => “…”, “storage” => “cache”, “metadata” => { … } } ““ An The temporary storage doesn’t need to be defined anymore if it’s not used. Any changes to When copying the S3 object to another location, any specified upload options will now be applied. Deprecation of passing unknown options to The The The The The Callback code from The } “‘ The The New The The The The The The The width & height validators in Any options passed to The The “‘rb plugin :pretty_location # “blogpost/aa357797-5845-451b-8662-08eecdc9f762/image/493g82jf23.jpg” plugin :pretty_location, class_underscore: :true # “blog_post/aa357797-5845-451b-8662-08eecdc9f762/image/493g82jf23.jpg” “‘ You can now load multiple persistence plugins simulatenously, and the correct one will be activated during persistence. The The The The The deprecated The The If you’re changing the attachment data column directly, you’ll now need to call The The The The The The The The The The The The The The The The The The options for The The The deprecated The The “‘rb # previous behaviour uploaded_file.storage_key #=> “store” # new behaviour uploaded_file.storage_key #=> :store “‘ The The “‘rb # this won’t work anymore def open(id) # … end # this is now required def open(id, **options) # … end “‘ The The support for The The The Specifying Non URI-escaped The The deprecated The The deprecated The deprecated The deprecated The The A custom downloader is now required to raise The deprecated The The deprecated The derivation block is now evaluated in context of a The derivation block can now return only The The The source The The Support for legacy The The The deprecated The deprecated The deprecated Passing a Rack uploaded file hash to The The Passing a block when loading the plugin is not supported anymore. The deprecated The private Failing to extract dimensions now prints out a warning by default. The private The The private The width & height validators will now raise an exception if Support for regexes has been dropped for MIME type and extension validators. The deprecated The plugin will now always prevent deletion of both replaced and destroyed attachments. The The The The Passing The rb photo = Photo.create(image: file) # promote original file to permanent storage photo.image_derivatives! # generate derivatives after promotion photo.save # save derivatives data Shrine Attacher.derivatives_processor :thumbnails do |original| # … endAttacher.derivatives_processor :crop do |original, left:, top:, width:, height:|
vips = ImageProcessing::Vips.source(original)
{ cropped: vips.crop!(left, top, width, height) }
end
rb photo.image_derivatives!(:thumbnails) photo.image_derivatives #=> { large: …, medium: …, small: … } photo.saveShrine # specify storage for all derivatives Attacher.derivatives_storage :other_store# or specify storage per derivative
Attacher.derivatives_storage { |derivative| :other_store }
rb photo = Photo.create(image: file) photo.image.storage_key #=> :storeAttacher redesign¶ ↑
Shrine::Attacher} class has been rewritten and can now be used without models:attacher = Shrine::Attacher.new
attacher.attach(file)
attacher.file #=> #<Shrine::UploadedFile>
Attacher#data, Attacher#load_data, and Attacher.from_data methods have been added for dumping and loading the attached file:# dump attached file into a serializable Hash
data = attacher.data #=> { "id" => "abc123.jpg", "storage" => "store", "metadata" => { ... } }
# initialize attacher from attached file data...
attacher = Shrine::Attacher.from_data(data)
attacher.file #=> #<Shrine::UploadedFile id="abc123.jpg" storage=:store metadata={...}>
# ...or load attached file into an existing attacher
attacher = Shrine::Attacher.new
attacher.load_data(data)
attacher.file #=> #<Shrine::UploadedFile>
Attacher#attach – attaches the file directly to permanent storageAttacher#attach_cached – extracted from Attacher#assignAttacher#upload – calls Shrine#upload, passing :record and :name contextAttacher#file – alias for Attacher#getAttacher#cache_key – returns temporary storage key (:cache by default)Attacher#store_key – returns permanent storage key (:store by default)Column¶ ↑
column} plugin adds the ability to serialize attached file data, in format suitable for writing into a database column.Shrine.plugin :column
# dump attached file data into a JSON string
data = attacher.column_data #=> '{"id":"abc123.jpg","storage":"store","metadata":{...}}'
# initialize attacher from attached file data...
attacher = Shrine::Attacher.from_column(data)
attacher.file #=> #<Shrine::UploadedFile id="abc123.jpg" storage=:store metadata={...}>
# ...or load attached file into an existing attacher
attacher = Shrine::Attacher.new
attacher.load_column(data)
attacher.file #=> #<Shrine::UploadedFile>
Entity¶ ↑
entity} plugin adds support for immutable structs, which are commonly used with ROM, Hanami and dry-rb.Shrine.plugin :entity
class Photo < Hanami::Entity
include Shrine::Attachment(:image)
end
photo = Photo.new(image_data: '{"id":"abc123.jpg","storage":"store","metadata":{...}}')
photo.image #=> #<Shrine::UploadedFile id="abc123.jpg" storage=:store ...>
Model¶ ↑
model} plugin adds support for mutable structs, which is used by activerecord and sequel plugins.Shrine.plugin :model
class Photo < Struct.new(:image_data)
include Shrine::Attachment(:image)
end
photo = Photo.new
photo.image = file
photo.image #=> #<Shrine::UploadedFile id="abc123.jpg" storage=:cache ...>
photo.image_data #=> #=> '{"id":"abc123.jpg", "storage":"cache", "metadata":{...}}'
Backgrounding rewrite¶ ↑
backgrounding} plugin has been rewritten for more flexibility and simplicity. The new usage is much more explicit:Shrine.plugin :backgrounding
Shrine::Attacher.promote_block do
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
end
Shrine::Attacher.destroy_block do
DestroyJob.perform_async(self.class.name, data)
end
class PromoteJob
include Sidekiq::Worker
def perform(attacher_class, record_class, record.id, name, file_data)
attacher_class = Object.const_get(attacher_class)
record = Object.const_get(record_class).find(record_id) # if using Active Record
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
attacher.atomic_promote
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
# attachment has changed or the record has been deleted, nothing to do
end
end
class DestroyJob
include Sidekiq::Worker
def perform(attacher_class, data)
attacher_class = Object.const_get(attacher_class)
attacher = attacher_class.from_data(data)
attacher.destroy
end
end
photo = Photo.new(photo_params)
photo.image_attacher.promote_block do |attacher|
PromoteJob.perform_async(
attacher.class.name,
attacher.record.class.name,
attacher.record.id,
attacher.name,
attacher.file_data,
current_user.id, # <== parameters from the controller
)
end
photo.save # will call our instance-level backgrounding hook
Persistence interface¶ ↑
activerecord, sequel) now implement a unified persistence interface:
Method
Description
‘Attacher#persist`
persists attachment data
‘Attacher#atomic_persist`
persists attachment data if attachment hasn’t changed
‘Attacher#atomic_promote`
promotes cached file and atomically persists changes
atomic_helpers} plugin, and are useful for background jobs. For example, this is how we’d use them to implement metadata extraction in the background in a concurrency-safe way:MetadataJob.perform_async(
attacher.class.name,
attacher.record.class.name,
attacher.record.id,
attacher.name,
attacher.file_data,
)
class MetadataJob
include Sidekiq::Worker
def perform(attacher_class, record_class, record_id, name, file_data)
attacher_class = Object.const_get(attacher_class)
record = Object.const_get(record_class).find(record_id) # if using Active Record
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
attacher.refresh_metadata! # extract metadata
attacher.atomic_persist # persist if attachment hasn't changed
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
# attachment has changed or record has been deleted, nothing to do
end
end
Other new plugins¶ ↑
mirroring} plugin has been added for replicating uploads and deletes to other storages. rb file = Shrine.upload(io, :store) # uploads to :store and :backup file.delete # deletes from :store and :backup “‘multi_cache} plugin has been added for allowing an attacher to accept files from additional temporary storages. rb photo.image = { “id” => “…”, “storage” => “cache”, “metadata” => { … } } photo.image.storage_key #=> :cache # or photo.image = { “id” => “…”, “storage” => “cache_one”, “metadata” => { … } } photo.image.storage_key #=> :cache_one # or photo.image = { “id” => “…”, “storage” => “cache_two”, “metadata” => { … } } photo.image.storage_key #=> :cache_two “‘form_assign} plugin has been added for assigning files directly from form params.rb Shrine.plugin :form_assign rb attacher = photo.image_attacher attacher.form_assign({ "image" => file, "title" => "...", "description" => "..." }) attacher.file #=> #<Shrine::UploadedFile id="..." storage=:cache ...> Other features¶ ↑
rb Shrine.plugin :model, cache: false rb photo.image = file photo.image.storage_key #=> :store (permanent storage) Shrine.download_response method has been added to the download_endpoint plugin for generating file response from the controller.rb Rails.application.routes.draw do get "/attachments" => "files#download" end “‘rb class FilesController < ApplicationController def download # … we can now perform things like authentication here … set_rack_response Shrine.download_response(env) endprivate
def set_rack_response((status, headers, body))
self.status = status
self.headers.merge!(headers)
self.response_body = body
end
Attacher#refresh_metadata! method has been added to refresh_metadata plugin. It refreshes metadata and writes new attached file data back into the data attribute.rb attacher.file.refresh_metadata! attacher.write # can now be shortened to attacher.refresh_metadata! Shrine::Attachment module now defines a .<name>_attacher class method on the target class.rb class Photo include ImageUploader::Attachment(:image) end rb Photo.image_attacher #=> #<ImageUploader::Attacher ...> JSON standard library is used)::validate option:rb attacher.assign(file, validate: { foo: "bar" }) rb class MyUploader < Shrine Attacher.validate do |**options| options #=> { foo: "bar" } end end validate: false.rb attacher.attach(file, validate: false) # skip validation close: false to Shrine#upload.delete: true to Shrine#upload.Attacher#file! method has been added for retrieving the attached file and raising and exception if it doesn’t exist.Derivation#opened method has been added for retrieving an opened derivative in derivation_endpoint plugin.Storage#delete_prefixed method has been added for deleting all files in specified directory.rb storage.delete_prefixed("some_directory/") Performance improvements¶ ↑
rb photo = Photo.find(photo_id) photo.image # parses and loads attached file photo.image # returns memoized attached file S3#open method doesn’t perform a #head_obect request anymore.derivation_endpoint plugin doesn’t perform a Storage#exists? call anymore when :upload is enabled.download_endpoint plugin doesn’t perform a Storage#exists? call anymore.Other Improvements¶ ↑
Core improvements¶ ↑
Shrine now works again with MRI 2.3.rb # Gemfile gem "shrine-memory" # this can be removed Attacher#assign method now accepts cached file data as a Hash.UploadedFile object can now be initialized with symbol keys.rb Shrine.uploaded_file(id: "...", storage: :store, metadata: { ... }) Shrine.storages will now be applied to existing Shrine and Attacher instances.FileSystem#open has been reverted. This allows users to continue using FileSystem storage in tests as a mock storage.Shrine#upload method now infers file extension from filename metadata, making possible to use filename to specify file extension.rb file = uploader.upload(StringIO.new("some text"), metadata: { "filename" => "file.txt" }) file.id #=> "2a2467ee6acbc5cb.txt" Shrine.opts hash is now deep-copied on subclassing. This allows plugins to freely mutate hashes and arrays in Shrine.opts, knowing they won’t be shared across subclasses.down dependency has been updated to ~> 5.0.Shrine::Attachment[] method has been added as an alternative syntax for creating attachment modules.rb class Photo include ImageUploader::Attachment[:image] end Plugin improvements¶ ↑
activerecord plugin now works with Active Record 3.activerecord and sequel plugin has been moved into attacher methods, allowing the user to override them.Attacher#(activerecord|sequel)_before_saveAttacher#(activerecord|sequel)_after_saveAttacher#(activerecord|sequel)_after_destroyurl_options plugin now allows you to override URL options by deleting them.rb uploaded_file.url(response_content_disposition: "attachment") “‘rb plugin :url_options, store: -> (io, options) { disposition = options.delete(:response_content_disposition, “inline”){
response_content_disposition: ContentDisposition.format(
disposition: disposition,
filename: io.original_filename,
)
}
:upload_options hash passed to the uploader is now merged with any options defined with the upload_options plugin.default_storage now evaluates the storage block in context of the Attacher instance.Attacher.default_cache and Attacher.default_store methods have been added to the default_storage plugin for declaratively setting default storage.rb Attacher.default_cache { ... } Attacher.default_store { ... } derivation_endpoint plugin now handles string derivation names.Derivation#upload method from derivation_endpoint plugin now accepts additional uploader optionsDerivation#upload method from derivation_endpoint plugin now accepts any IO-like object.derivation_endpoint plugin doesn’t re-open File objects returned in derivation block anymore.instrumentation plugin now instruments UploadedFile#open calls as a new open.shrine event. UploadedFile#download is still instrumented as download.shrine.upload.shrine event now has :metadata on the top level in instrumentation plugin.store_dimensions plugin don’t require UploadedFile#width and UploadedFile#height methods to be defined anymore, only that the corresponding metadata exists.Attacher#attach_cached are now forwarded to metadata extraction when restore_cached_data plugin is loaded.infer_extension plugin now works correctly with pretty_location plugin when pretty_location was loaded after infer_extension.pretty_location plugin now accepts :class_underscore option for underscoring class names.Backwards compatibility¶ ↑
Plugin deprecation and removal¶ ↑
backgrounding plugin has been rewritten and has a new API. While the new API works in a similar way, no backwards compatibility has been kept with the previous API.versions, processing, recache, and delete_raw plugins have been deprecated in favor of the new derivatives plugin.module_include plugin has been deprecated over overriding core classes directly.hooks, parallelize, parsed_json, and delete_promoted plugins have been removed.copy, backup, multi_delete, moving, logging, direct_upload background_helpers, and migration_helpers plugins have been removed.default_url_options plugin has been renamed to url_options.Attacher API¶ ↑
Attacher.new method now only accepts a hash of options, use Attacher.from_model for initializing from a model.Attacher#reload to make the attacher reflect those changes.Attacher#promote method now only saves the promoted file in memory, it doesn’t persist the changes.Attacher#_set and Attacher#set methods have been renamed to Attacher#set and Attacher#changed.Attacher#cache!, Attacher#store!, and Attacher#delete! methods have been removed.Attacher#_promote and Attacher#_delete methods have been removed.Attacher#swap, Attacher#update, Attacher#read, Attacher#write, Attacher#convert_to_data, Attacher#convert_before_write, and Attache#convert_after_read methods have been removed.Attacher.validate, Attacher#validate and Attacher#errors methods have been extracted into the new validation plugin.Attacher#data_attribute method has been renamed to Attacher#attribute.Attacher#replace method has been renamed to Attacher#destroy_previous.Attacher#assign method now raises an exception when non-cached uploaded file is assigned.Attacher#attached? method now returns whether a file is attached, regardless of whether it was changed or not.Attachment API¶ ↑
Shrine::Attachment module doesn’t define any instance methods by itself anymore. This has been moved into entity and model plugins.Uploader API¶ ↑
:phase option has been removed.Shrine#process and Shrine#processed methods have been removed.Shrine#store, Shrine#_store, Shrine#put, and Shrine#copy methods have been removed.Shrine#delete, Shrine#_delete, and Shrine#remove methods have been removed.Shrine#uploaded? method has been removed.Shrine.uploaded_file method doesn’t yield files anymore by default.Shrine#upload and Shrine#extract_metadata are now required to have symbol keys.Shrine.uploaded_file method now raises ArgumentError on invalid arguments.Shrine#upload method doesn’t rescue exceptions that happen in IO#close anymore.Shrine::IO_METHODS constant has been removed.Uploaded File API¶ ↑
UploadedFile method now extracts data from the given hash on initialization, it doesn’t store the whole hash anymore. This means the following potential code won’t work anymore:rb uploaded_file.id #=> "foo" uploaded_file.data["id"] = "bar" uploaded_file.id #=> "foo" UploadedFile#storage_key method now returns a Symbol instead of a String.UploadedFile#== method now requires both uploaded file objects to be of the same class.Storage API¶ ↑
Storage#open method is now required to accept additional options.Storage#open method is now required to raise Shrine::FileNotFound exception when the file is missing.S3 API¶ ↑
aws-sdk 2.x and aws-sdk-s3 < 1.14 has been removed.S3#open now raises Shrine::FileNotFound exception when S3 object doesn’t exist on the bucket.S3#upload method will now override any S3 object data when copying from another S3 object. If you were relying on S3 object data being inherited on copy, you will need to update your code.:host option in S3#initialize has been removed.:download option in S3#url has been removed.:multipart_threshold as an integer is not supported anymore.:content_disposition and :response_content_disposition values are not supported anymore.S3#presign method now returns a hash instead of an object for default POST method.S3#stream, S3#download, and S3#s3 methods have been removed.S3#open method doesn’t include an :object in Down::ChunkedIO#data anymore.FileSystem API¶ ↑
FileSystem#open now raises Shrine::FileNotFound exception when file does not exist on the filesystem.:host option in FileSystem#initialize has been removed.:older_than option in FileSystem#clear! has been removed.FileSystem#download method has been removed.FileSystem#movable? and FileSystem#move methods have been made private.FileSystem#open method doesn’t accept a block anymore.Plugins API¶ ↑
remote_urlShrine::Plugins::RemoteUrl::DownloadError in order for the exception to be converted into a validation error. Down::NotFound and Down::TooLarge exceptions are converted by default. All other exceptions are propagated.upload_endpointShrine::Plugins::UploadEndpoint::App constant has been removed.:request option holding the Rack::Request object isn’t passed to the uploader anymore.presign_endpointStorage#presign results that cannot coerce themselves into a Hash are not supported anymore.Shrine::Plugins::PresignEndpoint::App constant has been removed.derivation_endpointShrine::Derivation instance.File and Tempfile objects.:download_errors option has been removed, as it’s now obsolete.:include_uploaded_file option has been removed, as it’s now obsolete.UploadedFile is not passed to the derivation block anymore on download: false.Derivation#upload method now closes the uploaded file.derivation.upload instrumentation event payload now includes only :derivation key.download_endpoint/:storage/:id URLs has been dropped.:storages plugin option has been removed.Shrine::Plugins::DownloadEndpoint::App constant has been removed.data_uri:filename plugin option has been removed.Shrine::Plugins::DataUri::DataFile constant has been removed.rack_fileShrine::Plugins::RackFile::UploadedFile constant has been removed.Shrine#upload is not supported anymore.cached_attachment_data#<name>_cached_data= model method has been removed.Attacher#read_cached method has been renamed to Attacher#cached_data.default_urldetermine_mime_type:default analyzer alias has been removed.Shrine#mime_type_analyzers method has been removed.store_dimensionsShrine#extract_dimensions and Shrine#dimensions_analyzers methods have been removed.infer_extension:mini_mime inferrer is now the default inferrer, which requires the mini_mime gem.Shrine#infer_extension method has been removed.validation_helperswidth or height metadata is missing.versions:version_names, Shrine.version_names and Shrine.version? have been removed from versions plugin.keep_files:replaced and :destroyed options don’t have effect anymore.dynamic_storageShrine.dynamic_storages method has been removed.instrumentation:location, :upload_options, and :metadata keys have been removed from :options in upload.shrine event payload.:metadata key has been removed from metadata.shrine event payload.default_storagerecord & name arguments to the storage block has been deprecated over evaluating the block in context of the attacher instance.sequel:callbacks plugin option has been renamed to :hooks.