—
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#assign
Attacher#upload
– calls Shrine#upload
, passing :record
and :name
contextAttacher#file
– alias for Attacher#get
Attacher#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_save
Attacher#(activerecord|sequel)_after_save
Attacher#(activerecord|sequel)_after_destroy
url_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_url
Shrine::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_endpoint
Shrine::Plugins::UploadEndpoint::App
constant has been removed.:request
option holding the Rack::Request
object isn’t passed to the uploader anymore.presign_endpoint
Storage#presign
results that cannot coerce themselves into a Hash are not supported anymore.Shrine::Plugins::PresignEndpoint::App
constant has been removed.derivation_endpoint
Shrine::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_file
Shrine::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_url
determine_mime_type
:default
analyzer alias has been removed.Shrine#mime_type_analyzers
method has been removed.store_dimensions
Shrine#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_helpers
width
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_storage
Shrine.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_storage
record
& 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
.