Getting Started
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 appsNext 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:
- Sequel
- Active Record
- Rails
Sequel.migration do
change do
add_column :photos, :image_data, :text # or :jsonb
end
endclass AddImageDataToPhotos < ActiveRecord::Migration
def change
add_column :photos, :image_data, :text # or :jsonb
end
end$ rails generate migration add_image_data_to_photos image_data:text # or image_data:jsonbIf 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- Sequel
- Active Record
class Photo < Sequel::Model
include ImageUploader::Attachment(:image) # adds an `image` virtual attribute
endclass Photo < ActiveRecord::Base
include ImageUploader::Attachment(:image) # adds an `image` virtual attribute
endLet'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.
- Rails form builder
- Simple Form
- Forme
- HTML
form_for @photo do |f|
f.hidden_field :image, value: @photo.cached_image_data, id: nil
f.file_field :image
f.submit
endsimple_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
endform @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<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>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.
- Rails
- Sinatra
class PhotosController < ApplicationController
def create
Photo.create(photo_params)
# ...
end
private
def photo_params
params.require(:photo).permit(:image)
end
endpost "/photos" do
Photo.create(params[:photo])
# ...
endOnce 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:
- Rails
- HTML
<%= image_tag @photo.image_url %><img src="<%= @photo.image_url %>" />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 diskShrine::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 storagerequire "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
endIt'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