Last Update: 2017-04-04 15:16:39 +1000

The Design of Shrine

There are five main types of objects that you deal with in Shrine:

  • Storage

  • Shrine

  • Shrine::UploadedFile

  • Shrine::Attacher

  • Shrine::Attachment


On the lowest level we have a storage. A storage class encapsulates file management logic on a particular service. It is what actually performs uploads, generation of URLs, deletions and similar. By convention it is namespaced under Shrine::Storage.

filesystem ="uploads")
filesystem.upload(file, "foo")
filesystem.url("foo") #=> "uploads/foo"

A storage is a PORO which responds to certain methods:

class Shrine
  module Storage
    class MyStorage
      def upload(io, id, shrine_metadata: {}, **upload_options)
        # uploads `io` to the location `id`

      def open(id)
        # returns the remote file as an IO-like object

      def exists?(id)
        # checks if the file exists on the storage

      def delete(id)
        # deletes the file from the storage

      def url(id, options = {})
        # URL to the remote file, accepts options for customizing the URL

Storages are typically not used directly, but through Shrine.


A Shrine object (also called an “uploader”) acts as a wrapper around a storage. First the storage needs to be registered under a name:

Shrine.storages[:file_system] ="uploads")

Now we can instantiate an uploader with this identifier, and upload files:

uploader =
uploaded_file = uploader.upload(file)
uploaded_file #=> #<Shrine::UploadedFile>

The argument to Shrine#upload must be an IO-like object. The method does the following:

  • generates a unique location

  • extracts metadata

  • uploads the file

  • closes the file

  • creates a Shrine::UploadedFile from the data

In applications it's common to create subclasses of Shrine, in order to allow having different uploading logic for different types of files.


Shrine::UploadedFile represents a file that was uploaded to a storage, and is the result of Shrine#upload. It is essentially a wrapper around a data hash containing information about the uploaded file.

uploaded_file      #=> #<Shrine::UploadedFile> #=>
# {
#   "storage"  => "file_system",
#   "id"       => "9260ea09d8effd.pdf",
#   "metadata" => {
#     "filename"  => "resume.pdf",
#     "mime_type" => "application/pdf",
#     "size"      => 983294,
#   },
# }

The data hash contains the storage the file was uploaded to, the location, and some metadata: original filename, MIME type and filesize. The Shrine::UploadedFile object has handy methods which use this data:

# metadata methods
# ...

# storage methods
# ...

A Shrine::UploadedFile is itself an IO-like object (representing the remote file), so it can be passed to Shrine#upload as well.


We usually want to treat uploaded files as attachments to records, saving their data into a database column. This is the responsibility of Shrine::Attacher. A Shrine::Attacher uses Shrine uploaders and Shrine::UploadedFile objects internally.

The attaching process requires a temporary and a permanent storage to be registered (by default that's :cache and :store):

Shrine.storages = {

A Shrine::Attacher is instantiated with a model instance and an attachment name (an “image” attachment will be saved to image_data field):

attacher =, :image)

attacher.get #=> #<Shrine::UploadedFile>
attacher.record.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"

attacher.get #=> #<Shrine::UploadedFile>
attacher.record.image_data #=> "{\"storage\":\"store\",\"id\":\"ksdf02lr9sf3la.jpg\",\"metadata\":{...}}"

Above a file is assigned by the attacher, which “caches” (uploads) the file to the temporary storage. The cached file is then “promoted” (uploaded) to permanent storage. Behind the scenes a cached Shrine::UploadedFile is given to Shrine#upload, which works because Shrine::UploadedFile is an IO-like object. After both caching and promoting the data hash of the uploaded file is assigned to the record's column as JSON.

For more details see Using Attacher.


Shrine::Attachment is the highest level of abstraction. A Shrine::Attachment module exposes the Shrine::Attacher object through the model instance. The Shrine::Attachment class is a sublcass of Module, which means that an instance of Shrine::Attachment is a module: #=> true #=> [:image=, :image, :image_url, :image_attacher]

# equivalents

We can include this module to a model:

class Photo
photo.image = file # shorthand for `photo.image_attacher.assign(file)`
photo.image        # shorthand for `photo.image_attacher.get`
photo.image_url    # shorthand for `photo.image_attacher.url`

photo.image_attacher #=> #<Shrine::Attacher>

When an ORM plugin is loaded, the Shrine::Attachment module also automatically:

  • syncs Shrine's validation errors with the record

  • triggers promoting after record is saved

  • deletes the uploaded file if attachment was replaced/removed or the record destroyed