— id: getting-started
title: Getting Started¶ ↑
import Tabs from ‘@theme/Tabs’; import TabItem from ‘@theme/TabItem’;
Quick start¶ ↑
Add Shrine
to the Gemfile and write an initializer which sets up the storage and loads integration for your persistence library:
# Gemfile gem "shrine", "~> 3.0"
require "shrine" require "shrine/storage/file_system" Shrine.storages = { cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), # temporary store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"), # permanent } Shrine.plugin :sequel # or :activerecord Shrine.plugin :cached_attachment_data # for retaining the cached file across form redisplays Shrine.plugin :restore_cached_data # re-extract metadata when attaching a cached file Shrine.plugin :rack_file # for non-Rails apps
Next decide how you will name the attachment attribute on your model, and run a migration that adds an <attachment>_data
text or JSON column, which Shrine
will use to store all information about the attachment:
<Tabs> <TabItem value=“sequel” label=“Sequel”>
Sequel.migration do change do add_column :photos, :image_data, :text # or :jsonb end end
</TabItem> <TabItem value=“activerecord” label=“Active Record”>
class AddImageDataToPhotos < ActiveRecord::Migration def change add_column :photos, :image_data, :text # or :jsonb end end
</TabItem> <TabItem value=“rails” label=“Rails”>
$ rails generate migration add_image_data_to_photos image_data:text # or image_data:jsonb
</TabItem> </Tabs>
If using jsonb
consider adding a gin index for fast key-value pair searchability within image_data
.
Now you can create an uploader class for the type of files you want to upload, and add a virtual attribute for handling attachments using this uploader to your model. If you do not care about adding plugins or additional processing, you can use Shrine::Attachment
.
class ImageUploader < Shrine # plugins and uploading logic end
<Tabs> <TabItem value=“sequel” label=“Sequel”>
class Photo < Sequel::Model include ImageUploader::Attachment(:image) # adds an `image` virtual attribute end
</TabItem> <TabItem value=“activerecord” label=“Active Record”>
class Photo < ActiveRecord::Base include ImageUploader::Attachment(:image) # adds an `image` virtual attribute end
</TabItem> </Tabs>
Let’s now add the form fields which will use this virtual attribute (NOT the <attachment>_data
column attribute). We need (1) a file field for choosing files, and (2) a hidden field for retaining the uploaded file in case of validation errors and for potential direct uploads.
<Tabs> <TabItem value=“rails” label=“Rails form builder”>
form_for @photo do |f| f.hidden_field :image, value: @photo.cached_image_data, id: nil f.file_field :image f.submit end
</TabItem> <TabItem value=“simple_form” label=“Simple Form”>
simple_form_for @photo do |f| f.input :image, as: :hidden, input_html: { value: @photo.cached_image_data } f.input :image, as: :file f.button :submit end
</TabItem> <TabItem value=“form” label=“Forme”>
form @photo, action: "/photos", enctype: "multipart/form-data" do |f| f.input :image, type: :hidden, value: @photo.cached_image_data f.input :image, type: :file f.button "Create" end
</TabItem> <TabItem value=“html” label=“HTML”>
<form action="/photos" method="post" enctype="multipart/form-data"> <input name="photo[image]" type="hidden" value="<%= @photo.cached_image_data %>" /> <input name="photo[image] "type="file" /> <input type="submit" value="Create" /> </form>
</TabItem> </Tabs>
Note that the file field needs to go after the hidden field, so that selecting a new file can always override the cached file in the hidden field. Also notice the enctype="multipart/form-data"
HTML attribute, which is required for submitting files through the form (the Rails form builder will automatically generate this for you).
When the form is submitted, in your router/controller you can assign the file from request params to the attachment attribute on the model.
<Tabs> <TabItem value=“rails” label=“Rails”>
class PhotosController < ApplicationController def create Photo.create(photo_params) # ... end private def photo_params params.require(:photo).permit(:image) end end
</TabItem> <TabItem value=“sinatra” label=“Sinatra”>
post "/photos" do Photo.create(params[:photo]) # ... end
</TabItem> </Tabs>
Once a file is uploaded and attached to the record, you can retrieve a URL to the uploaded file with #<attachment>_url
and display it on the page:
<Tabs> <TabItem value=“rails” label=“Rails”>
<%= image_tag @photo.image_url %>
</TabItem> <TabItem value=“html” label=“HTML”>
<img src="<%= @photo.image_url %>" />
</TabItem> </Tabs>
Storage¶ ↑
A “storage” in Shrine
is an object that encapsulates communication with a specific storage service, by implementing a common public interface. Storage instances are registered under an identifier in Shrine.storages
, so that they can later be used by uploaders.
Shrine
ships with the following storages:
-
{
Shrine::Storage::FileSystem
} – stores files on disk -
{
Shrine::Storage::S3
} – stores files on AWS S3 (or DigitalOcean Spaces, MinIO, …) -
{
Shrine::Storage::Memory
} – stores file in memory (convenient for testing)
Here is how we might configure Shrine
with S3 storage:
# Gemfile gem "aws-sdk-s3", "~> 1.14" # for AWS S3 storage
require "shrine/storage/s3" s3_options = { bucket: "<YOUR BUCKET>", # required region: "<YOUR REGION>", # required access_key_id: "<YOUR ACCESS KEY ID>", secret_access_key: "<YOUR SECRET ACCESS KEY>", } Shrine.storages = { cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options), # temporary store: Shrine::Storage::S3.new(**s3_options), # permanent }
The above example sets up S3 for both temporary and permanent storage, which is suitable for direct uploads. The :cache
and :store
names are special only in terms that the attacher will automatically pick them up, you can also register more storage objects under different names.
See the FileSystem/S3/Memory storage docs for more details. There are many more Shrine storages provided by external gems, and you can also create your own storage.
Uploader¶ ↑
Uploaders are subclasses of Shrine
, and they wrap the actual upload to the storage. They perform common tasks around upload that aren’t related to a particular storage.
class MyUploader < Shrine # image attachment logic end
It’s common to create an uploader for each type of file that you want to handle (ImageUploader
, VideoUploader
, AudioUploader
etc), but really you can organize them in any way you like.
Uploading¶ ↑
The main method of the uploader is Shrine.upload
, which takes an IO-like object and a storage identifier on the input, and returns a representation of the uploaded file on the output.
MyUploader.upload(file, :store) #=> #<Shrine::UploadedFile>
Internally this instantiates the uploader with the storage and calls Shrine#upload
:
uploader = MyUploader.new(:store) uploader.upload(file) #=> #<Shrine::UploadedFile>
Some of the tasks performed by #upload
include:
-
extracting metadata
-
generating location
-
uploading (this is where the storage is called)
-
closing the uploaded file
The second argument is a “context” hash which is forwarded to places like metadata extraction and location generation, but it has a few special options:
uploader.upload(io, metadata: { "foo" => "bar" }) # add metadata uploader.upload(io, location: "path/to/file") # specify custom location uploader.upload(io, upload_options: { acl: "public-read" }) # add options to Storage#upload
IO abstraction¶ ↑
Shrine
is able to upload any IO-like object that implement methods {#read
}, {#rewind
}, {#eof?
} and {#close
} whose behaviour matches the {IO
} class. This includes but is not limited to the following objects:
-
{
File
} -
{
Tempfile
} -
{
StringIO
} -
{
ActionDispatch::Http::UploadedFile
} -
{
Shrine::RackFile
} -
{
Shrine::DataFile
} -
{
Shrine::UploadedFile
} -
{
Down::ChunkedIO
} -
…
uploader.upload File.open("/path/to/file", binmode: true) # upload from disk uploader.upload StringIO.new("file content") # upload from memory uploader.upload ActionDispatch::Http::UploadedFile.new(...) # upload from Rails controller uploader.upload Shrine.rack_file({ tempfile: tempfile }) # upload from Rack controller uploader.upload Rack::Test::UploadedFile.new(...) # upload from rack-test uploader.upload Down.open("https://example.org/file") # upload from internet uploader.upload Shrine::UploadedFile.new(...) # upload from Shrine storage
Uploaded file¶ ↑
The Shrine::UploadedFile
object represents the file that was uploaded to a storage, and it’s what’s returned from Shrine#upload
or when retrieving a record attachment.
uploader.upload(file) #=> #<Shrine::UploadedFile ...> (uploader) photo.image #=> #<Shrine::UploadedFile ...> (attachment) attacher.file #=> #<Shrine::UploadedFile ...> (attacher)
An uploaded file object contains the following data:
Key | Description |
---|---|
‘id` | location of the file on the storage |
‘storage` | identifier of the storage the file was uploaded to |
‘metadata` | file [metadata] that was extracted before upload |
uploaded_file #=> #<Shrine::UploadedFile id="949sdjg834.jpg" storage=:store metadata={...}> uploaded_file.id #=> "949sdjg834.jpg" uploaded_file.storage_key #=> :store uploaded_file.storage #=> #<Shrine::Storage::S3> uploaded_file.metadata #=> {...}
It comes with many convenient methods that delegate to the storage:
uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/949sdjg834.jpg" uploaded_file.open { |io| ... } # opens the uploaded file stream uploaded_file.download { |file| ... } # downloads the uploaded file to disk uploaded_file.stream(destination) # streams uploaded content into a writable destination uploaded_file.exists? #=> true uploaded_file.delete # deletes the uploaded file from the storage
It also implements the IO-like interface that conforms to Shrine’s IO abstraction, which allows it to be uploaded again to other storages.
uploaded_file.read # returns content of the uploaded file uploaded_file.eof? # returns true if the whole IO was read uploaded_file.rewind # rewinds the IO uploaded_file.close # closes the IO
For more details, see the Retrieving Uploads guide and {Shrine::UploadedFile
} API docs.
Attaching¶ ↑
To attach uploaded files to database records, Shrine
offers an attachment interface built on top of uploaders and uploaded files. There are integrations for various persistence libraries (ActiveRecord, Sequel, ROM, Hanami, Mongoid), but you can also attach files to plain structs (mutable or immutable).
Shrine.plugin :sequel # :activerecord
Attachment module¶ ↑
The easiest way to attach files is with the Shrine::Attachment
module:
class Photo < Sequel::Model # ActiveRecord::Base include ImageUploader::Attachment.new(:image) # include ImageUploader::Attachment[:image] # use your preferred syntax include ImageUploader::Attachment(:image) # end
The included module will add attachment methods for the specified attribute:
Method | Description |
---|---|
‘#image=` | uploads the file to temporary storage and serializes the result into ‘image_data` |
‘#image` | returns [‘Shrine::UploadedFile`][uploaded file] instantiated from `image_data` |
‘#image_url` | calls ‘url` on the attachment if it’s present, otherwise returns nil |
‘#image_attacher` | returns instance of [‘Shrine::Attacher`] which handles the attaching |
The persistence plugin we loaded will add callbacks that ensure cached files are automatically promoted to permanent storage on when record is saved, and that attachments are deleted when the record is destroyed.
# no file is attached photo.image #=> nil # the assigned file is cached to temporary storage and written to `image_data` column photo.image = File.open("waterfall.jpg", "rb") photo.image #=> #<Shrine::UploadedFile ...> photo.image_url #=> "/uploads/cache/0sdfllasfi842.jpg" photo.image_data #=> '{"id":"0sdfllasfi842.jpg","storage":"cache","metadata":{...}}' # the cached file is promoted to permanent storage and saved to `image_data` column photo.save photo.image #=> #<Shrine::UploadedFile ...> photo.image_url #=> "/uploads/store/l02kladf8jlda.jpg" photo.image_data #=> '{"id":"l02kladf8jlda.jpg","storage":"store","metadata":{...}}' # the attached file is deleted with the record photo.destroy photo.image.exists? #=> false
If there is already a file attached and a new file is attached, the previous attachment will get deleted when the record gets saved.
photo.update(image: new_file) # changes the attachment and deletes previous photo.update(image: nil) # removes the attachment and deletes previous
Attacher¶ ↑
The methods and callbacks added by the Shrine::Attachment
module just delegate the behaviour to an underlying Shrine::Attacher
object.
photo.image_attacher #=> #<Shrine::Attacher>
The Shrine::Attacher
object can be instantiated and used directly:
attacher = ImageUploader::Attacher.from_model(photo, :image) attacher.assign(file) # equivalent to `photo.image = file` attacher.file # equivalent to `photo.image` attacher.url # equivalent to `photo.image_url`
The attacher is what drives attaching files to model instances; you can use it as a more explicit alternative to models’ attachment interface, or when you need something that’s not available through the attachment methods.
See Using Attacher guide for more details.
Temporary storage¶ ↑
Shrine
uses temporary storage to support file validation and direct uploads. If you don’t need these features, you can tell Shrine
to upload files directly to permanent storage:
Shrine.plugin :model, cache: false
photo.image = File.open("waterfall.jpg", "rb") photo.image.storage_key #=> :store
If you’re using the attacher directly, you can just use Attacher#attach
instead of Attacher#assign
:
attacher.attach File.open("waterfall.jpg", "rb") attacher.file.storage_key #=> :store
Plugin system¶ ↑
By default, Shrine
comes with a small core which provides only the essential functionality. All additional features are available via plugins, which also ship with Shrine
. This way you can choose exactly what and how much Shrine
does for you, and you load the code only for features that you use.
Shrine.plugin :instrumentation # adds instrumentation
Plugins add behaviour by extending Shrine
core classes via module inclusion, and many of them also accept configuration options. The plugin system respects inheritance, so you can choose to load a plugin globally or per uploader.
class ImageUploader < Shrine plugin :store_dimensions # extract image dimensions only for this uploader and its descendants end
If you want to extend Shrine
functionality with custom behaviour, you can also create your own plugin. There are also additional external plugins created by others.
NOTE: An uploader class will inherit a copy of current superclass’ plugin options at the time of subclassing. This means you should not load additional plugins on a superclass after the subclass has already been created, because new options will not get applied to the subclass, which can result in errors.
Metadata¶ ↑
Shrine
automatically extracts some basic file metadata and saves them to the Shrine::UploadedFile
. You can access them through the #metadata
hash or via metadata methods:
uploaded_file.metadata #=> # { # "filename" => "matrix.mp4", # "mime_type" => "video/mp4", # "size" => 345993, # } uploaded_file.original_filename #=> "matrix.mp4" uploaded_file.extension #=> "mp4" uploaded_file.mime_type #=> "video/mp4" uploaded_file.size #=> 345993
MIME type¶ ↑
By default, mime_type
metadata will be set from the #content_type
attribute of the uploaded file (if it exists), which is generally not secure and will trigger a warning. You can load the {determine_mime_type
} plugin to have MIME type extracted from file content instead.
# Gemfile gem "marcel", "~> 0.3"
Shrine.plugin :determine_mime_type, analyzer: :marcel
photo = Photo.new(image: StringIO.new("<?php ... ?>")) photo.image.mime_type #=> "application/x-php"
Other metadata¶ ↑
In addition to basic metadata, you can also extract image dimensions, calculate signatures, and in general extract any custom metadata. Check out the Extracting Metadata guide for more details.
Processing¶ ↑
Shrine
allows you to process attached files both “eagerly” and “on-the-fly”. For example, if your app is accepting image uploads, you can generate a predefined set of thumbnails when the image is attached to a record, or you can have thumbnails generated dynamically as they’re needed.
For image processing, it’s recommended to use the {ImageProcessing}[https://github.com/janko/image_processing] gem, which is a high-level wrapper for processing with MiniMagick and libvips.
$ brew install imagemagick vips
Eager processing¶ ↑
We can use the {derivatives
} plugin to generate a pre-defined set of processed files (e.g. image thumbnails). We do this by registering a derivatives processor block and then explicitly triggering creation:
# Gemfile gem "image_processing", "~> 1.8"
Shrine.plugin :derivatives, create_on_promote: true
require "image_processing/mini_magick" class ImageUploader < Shrine Attacher.derivatives 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(image: file) photo.save # automatically creates derivatives on promotion
You can then retrieve the URL of a processed derivative:
photo.image_url(:large) #=> "https://s3.amazonaws.com/path/to/large.jpg"
The derivatives data is stored in the <attachment>_data
column, and you can retrieve them as {Shrine::UploadedFile
} objects:
photo.image(:large) #=> #<Shrine::UploadedFile id="path/to/large.jpg" storage=:store metadata={...}> photo.image(:large).url #=> "https://s3.amazonaws.com/path/to/large.jpg" photo.image(:large).size #=> 5825949 photo.image(:large).mime_type #=> "image/jpeg"
For more details, see the File Processing guide and the {derivatives
} plugin documentation.
On-the-fly processing¶ ↑
On-the-fly processing is provided by the {derivation_endpoint
} plugin. To set it up, we configure the plugin with a secret key and a path prefix, mount its Rack app in our routes on the configured path prefix, and define processing we want to perform:
# Gemfile gem "image_processing", "~> 1.8"
# config/initializers/rails.rb (Rails) # ... Shrine.plugin :derivation_endpoint, secret_key: "<YOUR_SECRET_KEY>"
require "image_processing/mini_magick" class ImageUploader < Shrine plugin :derivation_endpoint, prefix: "derivations/image" # matches mount point derivation :thumbnail do |file, width, height| ImageProcessing::MiniMagick .source(file) .resize_to_limit!(width.to_i, height.to_i) end end
# config/routes.rb (Rails) Rails.application.routes.draw do # ... mount ImageUploader.derivation_endpoint => "/derivations/image" end
Now we can generate URLs from attached files that will perform the desired processing:
photo.image.derivation_url(:thumbnail, 600, 400) #=> "/derivations/image/thumbnail/600/400/eyJpZCI6ImZvbyIsInN0b3JhZ2UiOiJzdG9yZSJ9?signature=..."
The on-the-fly processing feature is highly customizable, see the {derivation_endpoint
} plugin documentation for more details.
Validation¶ ↑
The {validation
} plugin allows performing validation for attached files. For common validations, the {validation_helpers
} plugin provides useful validators for built in metadata:
Shrine.plugin :validation_helpers
class DocumentUploader < Shrine Attacher.validate do validate_max_size 5*1024*1024, message: "is too large (max is 5 MB)" validate_mime_type %w[application/pdf] end end
user = User.new user.cv = File.open("cv.pdf", "rb") user.valid? #=> false user.errors.to_hash #=> {:cv=>["is too large (max is 5 MB)"]}
For more details, see the File Validation guide and {validation_helpers
} plugin docs.
Location¶ ↑
Shrine
automatically generates random locations before uploading files. By default, the hierarchy is flat, meaning all files are stored in the root directory of the storage.
024d9fe83bf4fafb.jpg 768a336bf54de219.jpg adfaa363629f7fc5.png ...
The {pretty_location
} plugin provides a good default hierarchy:
Shrine.plugin :pretty_location
user/ 564/ avatar/ aa3e0cd715.jpg thumb-493g82jf23.jpg photo/ 123/ image/ 13f8a7bc18.png thumb-9be62da67e.png ...
But you can also override Shrine#generate_location
with a custom implementation, for example:
class ImageUploader < Shrine def generate_location(io, record: nil, derivative: nil, **) return super unless record table = record.class.table_name id = record.id prefix = derivative || "original" "uploads/#{table}/#{id}/#{prefix}-#{super}" end end
uploads/ photos/ 123/ original-afe929b8b4.jpg small-ad61f25883.jpg medium-41b75c42bb.jpg large-73e67abe50.jpg ...
There should always be a random component in the location, so that the ORM dirty tracking is detected properly.
The Shrine#generate_location
method contains a lot of useful context for the upcoming upload:
class ImageUploader < Shrine def generate_location(io, record: nil, name: nil, derivative: nil, metadata: {}, **options) storage_key #=> :cache, :store, ... io #=> #<File>, #<Shrine::UploadedFile>, ... record #=> #<Photo>, #<User>, ... name #=> :image, :avatar, ... derivative #=> :small, :medium, :large, ... (derivatives plugin) metadata #=> { "filename" => "nature.jpg", "mime_type" => "image/jpeg", "size" => 18573, ... } options #=> { ... other uploader options ... } # ... end end
Direct uploads¶ ↑
To improve the user experience, it’s recommended to upload files asynchronously as soon as the user selects them. The direct uploads would go to temporary storage, just like in the synchronous flow. Then, instead of attaching a raw file to your model, you assign the cached file JSON data.
# in the regular synchronous flow photo.image = file # in the direct upload flow photo.image = '{"id":"...","storage":"cache","metadata":{...}}'
On the client side it’s highly recommended to use {Uppy}[https://uppy.io], a very flexible modern JavaScript file upload library that happens to integrate nicely with Shrine
.
Simple direct upload¶ ↑
The simplest approach is to upload directly to an endpoint in your app, which forwards uploads to the specified storage. The {upload_endpoint
} Shrine
plugin provides a mountable Rack app that implements this endpoint:
Shrine.plugin :upload_endpoint
# config/routes.rb (Rails) Rails.application.routes.draw do # ... mount Shrine.upload_endpoint(:cache) => "/upload" # POST /upload end
Then you can configure Uppy’s XHR Upload plugin to upload to this endpoint. See this walkthrough for adding simple direct uploads from scratch, it includes a complete JavaScript example (there is also the Roda / Rails demo app).
Presigned direct upload¶ ↑
For better performance, you can also upload files directly to your cloud storage service (AWS S3, Google Cloud Storage etc). For this, your temporary storage needs to be your cloud service:
require "shrine/storage/s3" Shrine.storages = { cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options), store: Shrine::Storage::S3.new(**s3_options) }
In this flow, the client needs to first fetch upload parameters from the server, and then use these parameters for the upload to the cloud service. The {presign_endpoint
} Shrine
plugin provides a mountable Rack app that generates upload parameters:
Shrine.plugin :presign_endpoint
# config/routes.rb (Rails) Rails.application.routes.draw do # ... mount Shrine.presign_endpoint(:cache) => "/s3/params" # GET /s3/params end
Then you can configure Uppy’s AWS S3 plugin to fetch params from your endpoint before uploading to S3. See this walkthrough for adding direct uploads to S3 from scratch, it includes a complete JavaScript example (there is also the Roda / Rails demo). See also the Direct Uploads to S3 guide for more details.
Resumable direct upload¶ ↑
If your app is accepting large uploads, you can improve resilience by making the uploads resumable. This can significantly improve experience for users on slow and flaky internet connections.
Uppy S3 Multipart¶ ↑
You can achieve resumable uploads directly to S3 with the AWS S3 Multipart Uppy plugin, accompanied with uppy_s3_multipart
Shrine
plugin provided by the uppy-s3_multipart gem.
# Gemfile gem "uppy-s3_multipart", "~> 0.3"
Shrine.plugin :uppy_s3_multipart
# config/routes.rb (Rails) Rails.application.routes.draw do # ... mount Shrine.uppy_s3_multipart(:cache) => "/s3/multipart" end
See the uppy-s3_multipart docs for more details.
Tus protocol¶ ↑
If you want a more generic approach, you can build your resumable uploads on {tus}[https://tus.io] – an open resumable upload protocol. On the server side you can use the tus-ruby-server gem, on the client side Uppy’s Tus plugin, and the shrine-tus gem for the glue.
# Gemfile gem "tus-server", "~> 2.0" gem "shrine-tus", "~> 2.1"
require "shrine/storage/tus" Shrine.storages = { cache: Shrine::Storage::Tus.new, # tus server acts as temporary storage store: ..., # your permanent storage }
# config/routes.rb (Rails) Rails.application.routes.draw do # ... mount Tus::Server => "/files" end
See this walkthrough for adding tus-powered resumable uploads from scratch, it includes a complete JavaScript example (there is also a demo app). See also shrine-tus and tus-ruby-server docs for more details.
Backgrounding¶ ↑
The {backgrounding
} plugin allows you to move file promotion and deletion into a background job, using the backgrounding library of your choice:
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
Clearing cache¶ ↑
Shrine
doesn’t automatically delete files uploaded to temporary storage, instead you should set up a separate recurring task that will automatically delete old cached files.
Most Shrine
storage classes come with a #clear!
method, which you can call in a recurring script. For FileSystem and S3 storage it would look like this:
# FileSystem storage file_system = Shrine.storages[:cache] file_system.clear! { |path| path.mtime < Time.now - 7*24*60*60 } # delete files older than 1 week
# S3 storage s3 = Shrine.storages[:cache] s3.clear! { |object| object.last_modified < Time.now - 7*24*60*60 } # delete files older than 1 week
For S3, it may be easier and cheaper to use S3 bucket lifecycle expiration rules instead.
Logging¶ ↑
The {instrumentation
} plugin sends and logs events for important operations:
Shrine.plugin :instrumentation, notifications: ActiveSupport::Notifications uploaded_file = Shrine.upload(io, :store) uploaded_file.exists? uploaded_file.download uploaded_file.delete
Metadata (32ms) – {:storage=>:store, :io=>StringIO, :uploader=>Shrine} Upload (1523ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :io=>StringIO, :upload_options=>{}, :uploader=>Shrine} Exists (755ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :uploader=>Shrine} Download (1002ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :download_options=>{}, :uploader=>Shrine} Delete (700ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :uploader=>Shrine}
Some plugins add their own instrumentation as well when they detect that the instrumentation
plugin has been loaded. For that to work, the instrumentation
plugin needs to be loaded before any of these plugins.
Plugin | Instrumentation |
---|---|
‘derivation_endpoint` | instruments file processing |
‘derivatives` | instruments file processing |
‘determine_mime_type` | instruments analyzing MIME type |
‘store_dimensions` | instruments extracting image dimensions |
‘signature` | instruments calculating signature |
‘infer_extension` | instruments inferring extension |
‘remote_url` | instruments remote URL downloading |
‘data_uri` | instruments data URI parsing |
For instrumentation, warnings, and other logging, Shrine
uses its internal logger. You can tell Shrine
to use a different logger. For example, if you’re using Rails, you might want to tell it to use the Rails logger:
Shrine.logger = Rails.logger
In tests you might want to tell Shrine
to log only warnings:
Shrine.logger.level = Logger::WARN