—
title: Upgrading from Refile¶ ↑
This guide is aimed at helping Refile users transition to Shrine
, and it consists of three parts:
-
Explanation of the key differences in design between Refile and
Shrine
-
Instructions how to migrate an existing app that uses Refile to
Shrine
-
Extensive reference of Refile’s interface with
Shrine
equivalents
Overview¶ ↑
Shrine
borrows many great concepts from Refile: Refile’s “backends” are here named “storages”, it uses the same IO abstraction for uploading and representing uploaded files, similar attachment logic, and direct uploads are supported as well.
Uploader¶ ↑
While in Refile you work with storages directly, Shrine
uses uploaders which wrap storage uploads:
storage = Shrine.storages[:store] storage #=> #<Shrine::Storage::S3> uploaded_file = Shrine.upload(image, :store) uploaded_file #=> #<Shrine::UploadedFile ...> uploaded_file.storage #=> #<Shrine::Storage::S3>
This way, Shrine
can perform tasks like generating location, extracting metadata, processing, and logging, which are all storage-agnostic, and leave storages to deal only with actual file storage. And these tasks can be configured differently depending on the types of files you’re uploading:
class ImageUploader < Shrine add_metadata :exif do |io| MiniMagick::Image.new(io).exif end end
class VideoUploader < Shrine add_metadata :duration do |io| FFMPEG::Movie.new(io.path).duration end end
URL¶ ↑
While Refile serves all files through the Rack endpoint mounted in your app, Shrine
serves files directly from storage services:
Refile.attachment_url(@photo, :image) #=> "/attachments/cache/50dfl833lfs0gfh.jpg"
@photo.image.url #=> "https://my-bucket.s3.amazonaws.com/cache/50dfl833lfs0gfh.jpg"
If you’re using storage which don’t expose files over URL (e.g. a database storage), or you want to secure your downloads, you can also serve files through your app using the {download_endpoint
} plugin.
Persistence¶ ↑
Refile persists the uploaded file location and metadata into individual columns:
-
<attachment>_id
-
<attachment>_filename
-
<attachment>_content_type
-
<attachment>_size
Shrine
, on the other hand, saves all uploaded file data into a single <attachment>_data
column:
{ "id": "path/to/image.jpg", "storage": "store", "metadata": { "filename": "nature.jpg", "size": 4739472, "mime_type": "image/jpeg" } }
photo.image.id #=> "path/to/image.jpg" photo.image.storage_key #=> :store photo.image.metadata #=> { "filename" => "...", "size" => ..., "mime_type" => "..." } photo.image.original_filename #=> "nature.jpg" photo.image.size #=> 4739472 photo.image.mime_type #=> "image/jpeg"
This column can be queried if it’s made a JSON column. Alternatively, you can use the {metadata_attributes
} plugin to save metadata into separate columns.
Processing¶ ↑
Shrine
provides on-the-fly processing via the {derivation_endpoint
} plugin:
require "image_processing/mini_magick" class ImageUploader < Shrine plugin :derivation_endpoint, secret_key: "<YOUR SECRET KEY>", prefix: "derivations/image" # needs to match the mount point in routes 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
Shrine
also support eager processing using the {derivatives
} plugin.
Validation¶ ↑
In Refile, file validation is defined statically on attachment definition:
class Photo < Sequel::Model attachment :image, extension: %w[jpg jpeg png webp], content_type: %w[image/jpeg image/png image/webp] end
In Shrine
, validation is performed on the instance-level, which allows you to make the validation conditional:
class ImageUploader < Shrine plugin :validation_helpers Attacher.validate do validate_max_size 10*1024*1024 validate_extension %w[jpg jpeg png webp] if validate_mime_type %w[image/jpeg image/png image/webp] validate_max_dimensions [5000, 5000] end end end
Refile extracts the MIME type from the file extension, which means it can easily be spoofed (just give a PHP file a .jpg
extension). Shrine
has the {determine_mime_type
} plugin for determining MIME type from file content.
Direct uploads¶ ↑
Shrine
borrows Refile’s idea of direct uploads, and ships with upload_endpoint
and presign_endpoint
plugins which provide endpoints for uploading files and generating presigns.
Shrine.plugin :upload_endpoint Shrine.upload_endpoint(:cache) # Rack app that uploads files to specified storage Shrine.plugin :upload_endpoint Shrine.presign_endpoint(:cache) # Rack app that generates presigns for specified storage
While Refile ships with a plug-and-play JavaScript for direct uploads, Shrine
instead adopts Uppy, a modern and modular JavaScript file upload library that happens to integrate well with Shrine
.
Multiple uploads¶ ↑
Shrine
doesn’t have support for multiple uploads out-of-the-box like Refile does. Instead, you can implement them using a separate table with a one-to-many relationship to which the files will be attached. The Multiple Files guide explains this setup in more detail.
Migrating from Refile¶ ↑
You have an existing app using Refile and you want to transfer it to Shrine
. Let’s assume we have a Photo
model with the “image” attachment.
1. Add Shrine
column¶ ↑
First we need to create the image_data
column for Shrine:
add_column :photos, :image_data, :text
2. Dual write¶ ↑
Afterwards we need to make new uploads write to the image_data
column. This can be done by including the below module to all models that have Refile attachments:
require "shrine" Shrine.storages = { cache: ..., store: ..., } Shrine.plugin :model module RefileShrineSynchronization def write_shrine_data(name) attacher = Shrine::Attacher.from_model(self, name) if read_attribute("#{name}_id").present? attacher.set shrine_file(name) else attacher.set nil end end def shrine_file(name) Shrine.uploaded_file( storage: :store, id: send("#{name}_id"), metadata: { "size" => (send("#{name}_size") if respond_to?("#{name}_size")), "filename" => (send("#{name}_filename") if respond_to?("#{name}_filename")), "mime_type" => (send("#{name}_content_type") if respond_to?("#{name}_content_type")), } ) end end
class Photo < ActiveRecord::Base attachment :image include RefileShrineSynchronization before_save do write_shrine_data(:image) if image_id_changed? end end
After you deploy this code, the image_data
column should now be successfully synchronized with new attachments.
3. Data migration¶ ↑
Next step is to run a script which writes all existing Refile attachments to image_data
:
Photo.find_each do |photo| photo.write_shrine_data(:image) photo.save! end
4. Rewrite code¶ ↑
Now you should be able to rewrite your application so that it uses Shrine
instead of Refile (you can consult the reference in the next section). You can remove the RefileShrineSynchronization
module as well.
5. Remove Refile columns¶ ↑
If everything is looking good, we can remove Refile columns:
remove_column :photos, :image_id remove_column :photos, :image_size remove_column :photos, :image_filename remove_column :photos, :image_content_type
Refile to Shrine
direct mapping¶ ↑
Refile
¶ ↑
.cache
, .store
, .backends
¶ ↑
Shrine
calles these “storages”, and it doesn’t have special accessors for :cache
and :store
:
Shrine.storages = { cache: Shrine::Storage::Foo.new(*args), store: Shrine::Storage::Bar.new(*args), }
.app
, .mount_point
, .automount
¶ ↑
The Rack apps provided by the *_endpoint
Shrine
plugins are mounted explicitly:
# config/routes.rb Rails.application.routes.draw do # adds `POST /images/upload` endpoint mount ImageUploader.upload_endpoint(:cache) => "/images/upload" end
.allow_uploads_to
¶ ↑
The Shrine.upload_endpoint
and Shrine.presign_endpoint
builders require you to specify the storage that will be used.
.logger
¶ ↑
Shrine.logger
.processors
, .processor
¶ ↑
class ImageUploader < Shrine plugin :derivatives derivation :thumbnail do |file, width, height| # ... end end
.types
¶ ↑
Shrine
defines validations on the uploader class level:
class MyUploader < Shrine plugin :validation_helpers Attacher.validate do validate_max_size 5*1024*1024 end end
.extract_filename
¶ ↑
Shrine’s equivalent is a Shrine#extract_filename
private method. You can instead use the Shrine#extract_metadata
public method.
.extract_content_type
¶ ↑
The {determine_mime_type
} plugin provides a Shrine.determine_mime_type
method.
.app_url
, .upload_url
, .attachment_upload_url
, .presign_url
, .attachment_presign_url
¶ ↑
Shrine
requires you to use your framework to generate URLs to mounted endpoints.
.attachment_url
, .file_url
¶ ↑
You can call #url
on the uploaded file, or #<name>_url
on the model. Alternatively, you can use #download_url
provided by the download_endpoint
plugin.
.host
, .cdn_host
, .app_host
, .allow_downloads_from
, allow_origin
, .content_max_age
¶ ↑
These can be configured on individual *_endpoint
plugins.
.secret_key
, .token
, .valid_token?
¶ ↑
The secret key is required for the {derivation_endpoint
}, but these methods are not exposed.
Attachment
¶ ↑
Shrine’s equivalent to calling the attachment is including an attachment module of an uploader:
class Photo include ImageUploader::Attachment(:image) end
:extension
, :content_type
, :type
¶ ↑
In Shrine
validations are done instance-level inside the uploader, most commonly with the validation_helpers
plugin:
class ImageUploader < Shrine plugin :validation_helpers Attacher.validate do validate_extension %w[jpg jpeg png] validate_mime_type %w[image/jpeg image/png] end end
:cache
, :store
¶ ↑
Shrine
provides a default_storage
plugin for setting custom storages on the uploader:
Shrine.storages[:custom_cache] = Shrine::Storage::Foo.new(*args) Shrine.storages[:custom_store] = Shrine::Storage::Bar.new(*args)
class ImageUploader < Shrine plugin :default_storage, cache: :custom_cache, store: :custom_store end
:raise_errors
¶ ↑
No equivalent currently exists in Shrine
.
accepts_attachments_for
¶ ↑
No equivalent in Shrine
, but take a look at the Multiple Files guide.
Form helpers¶ ↑
attachment_field
¶ ↑
The following Refile code
form_for @user do |form| form.attachment_field :profile_image end
is equivalent to the following Shrine
code
Shrine.plugin :cached_attachment_data
form_for @user do |form| form.hidden_field :profile_image, value: @user.cached_profile_image_data, id: nil form.file_field :profile_image end
Model methods¶ ↑
remove_<attachment>
¶ ↑
Shrine
comes with a remove_attachment
plugin which adds the same #remove_<attachment>
method to the model.
Shrine.plugin :remove_attachment
form_for @user do |form| form.hidden_field :profile_image, value: @user.cached_profile_image_data, id: nil form.file_field :profile_image form.check_box :remove_profile_image end
remote_<attachment>_url
¶ ↑
Shrine
comes with a remote_url
plugin which adds the same #<attachment>_remote_url
method to the model.
Shrine.plugin :remote_url
form_for @user do |form| form.hidden_field :profile_image, value: @user.cached_profile_image_data, id: nil form.file_field :profile_image form.text_field :profile_image_remote_url end