Skip to main content

Advantages of Shrine

There are many existing file upload solutions for Ruby out there. This guide will attempt to cover some of the main advantages that Shrine offers compared to these alternatives.

For a more direct comparison with specific file attachment libraries, there are more specialized guides for CarrierWave, Paperclip, and Refile users.

Generality

Many alternative file upload solutions are coupled to either Rails (Active Storage) or Active Record itself (Paperclip, Dragonfly). This is not ideal, as Rails-specific solutions fragment the Ruby community between developers that use Rails and developers that don't. There are many great web frameworks (Sinatra, Roda, Cuba, Hanami, Grape) and persistence libraries (Sequel, ROM, Hanami::Model) out there that people use instead of Rails and Active Record.

Shrine, on the other hand, doesn't make any assumptions about which web framework or persistence library you're using. Any web-specific functionality is implemented on top of Rack, the Ruby web server interface that powers all the popular Ruby web frameworks (including Rails). The integrations for specific ORMs are provided as plugins.

# Rack-based plugins Shrine.plugin :upload_endpoint Shrine.plugin :presign_endpoint Shrine.plugin :download_endpoint Shrine.plugin :derivation_endpoint Shrine.plugin :rack_response Shrine.plugin :rack_file  # ORM plugins Shrine.plugin :activerecord Shrine.plugin :sequel Shrine.plugin :mongoid # https://github.com/shrinerb/shrine-mongoid Shrine.plugin :rom # https://github.com/shrinerb/shrine-rom Shrine.plugin :hanami # https://github.com/katafrakt/hanami-shrine

Simplicity

Where some popular file attachment libraries have god objects (CarrierWave::Uploader::Base and Paperclip::Attachment), Shrine distributes responsibilities across multiple core classes:

ClassDescription
Shrine::Storage::*Encapsulate file operations for the underlying service
ShrineWraps uploads and handles loading plugins
Shrine::UploadedFileRepresents a file that was uploaded to a storage
Shrine::AttacherHandles attaching files to records
Shrine::AttachmentAdds convenience attachment methods to model instances
photo.image #=> #<Shrine::UploadedFile> photo.image.storage #=> #<Shrine::Storage::S3> photo.image.uploader #=> #<Shrine> photo.image_attacher #=> #<Shrine::Attacher> photo.class.ancestors #=> [..., #<Shrine::Attachment(:image)>, ...]

The attachment functionality is decoupled from persistence and storage, which makes it much easier to reason about. Also, special care was taken to make integrating new storages and persistence libraries as easy as possible.

Modularity

Shrine uses a plugin system that allows you to pick and choose the features that you want. Moreover, you'll only be loading code for the features you've selected, which means that Shrine will generally load much faster than the alternatives.

Shrine.plugin :instrumentation  # which translates to  require "shrine/plugins/instrumentation" Shrine.plugin Shrine::Plugins::Instrumentation
Shrine.method(:instrument).owner #=> Shrine::Plugins::Instrumentation::ClassMethods

Shrine recommends a certain type of attachment flow, but it still offers good low-level abstractions that give you the flexibility to build your own flow.

uploaded_file = ImageUploader.upload(image, :store) # metadata extraction, upload location generation uploaded_file.id #=> "44ccafc10ce6a4ff22829e8f579ee6b9.jpg" uploaded_file.metadata #=> { ... extracted metadata ... }  data = uploaded_file.to_json # serialization # ... uploaded_file = ImageUploader.uploaded_file(data) # deserialization  uploaded_file.url #=> "https://..." uploaded_file.download { |tempfile| ... } # streaming download uploaded_file.delete

Dependencies

Shrine is very diligent when it comes to dependencies. It has two mandatory dependencies – Down and ContentDisposition – which are loaded only by components that need them. Some Shrine plugins also require additional dependencies, but you only need to load them if you're using those plugins.

Moreover, Shrine often gives you the ability choose between multiple alternative dependencies for doing the same task. For example, the determine_mime_type plugin allows you to choose between the file command, FileMagic, FastImage, MimeMagic, or Marcel gem for determining the MIME type, while the store_dimensions plugin can extract dimensions using FastImage, MiniMagick, or ruby-vips gem.

Shrine.plugin :determine_mime_type, analyzer: :marcel Shrine.plugin :store_dimensions, analyzer: :mini_magick

Inheritance

Shrine is designed to handle any types of files. If you're accepting uploads of multiple types of files, such as videos and images, chances are that the logic for handling them will differ:

  • small images can be processed on-the-fly, but large files should be processed in a background job
  • you might want to store different files to different storage services (images, documents, audios, videos)
  • extracting metadata might require different tools depending on the filetype

With Shrine you can create isolated uploaders for each type of file. For features you want all uploaders to share, their plugins can be loaded globally, while other plugins you can load only for selected uploaders.

# loaded for all plugins Shrine.plugin :activerecord Shrine.plugin :instrumentation
class ImageUploader < Shrine  # loaded only for ImageUploader  plugin :store_dimensions end
class VideoUploader < Shrine  # loaded only for VideoUploader  plugin :default_storage, store: :vimeo end

Processing

Most file attachment libraries allow you to process files either "eagerly" (Paperclip, CarrierWave) or "on-the-fly" (Dragonfly, Refile, Active Storage). However, each approach is suitable for different requirements. For instance, while on-the-fly processing is suitable for fast processing (image thumbnails, document previews), longer running processing (video transcoding, raw images) should be moved into a background job.

That's why Shrine supports both eager and on-the-fly processing. For example, if you're handling image uploads, you can choose to either generate a set of pre-defined thumbnails during attachment:

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.image_derivatives! # creates thumbnails photo.image_url(:large) #=> "https://s3.amazonaws.com/path/to/large.jpg"

or generate thumbnails on-demand:

class ImageUploader < Shrine  derivation :thumbnail do |file, width, height|  ImageProcessing::MiniMagick  .source(file)  .resize_to_limit!(width.to_i, height.to_i)  end end
photo.image.derivation_url(:thumbnail, 600, 400) #=> ".../thumbnail/600/400/eyJpZCI6ImZvbyIsInN0b3JhZ2UiOiJzdG9yZSJ9?signature=..."

ImageMagick

Many file attachment libraries, such as CarrierWave, Paperclip, Dragonfly and Refile, implement their own image processing macros. Rather than building yet another in-house implementation, a general purpose ImageProcessing gem was created instead, which works great with Shrine.

require "image_processing/mini_magick"  thumbnail = ImageProcessing::MiniMagick  .source(image)  .resize_to_limit(400, 400)  .call # convert input.jpg -auto-orient -resize 400x400> -sharpen 0x1 output.jpg  thumbnail #=> #<Tempfile:/var/folders/.../image_processing20180316-18446-1j247h6.jpg>

It takes care of many details for you, such as auto orienting the input image and applying sharpening to resized images. It also has support for libvips.

libvips

libvips is a full-featured image processing library like ImageMagick, with great performance characteristics. It's often multiple times faster than ImageMagick, and also has lower memory usage. For more details, see Why is libvips quick.

The ImageProcessing gem provides libvips support as an alternative ImageProcessing::Vips backend, sharing the same API as the ImageProcessing::MiniMagick backend.

require "image_processing/vips"  # this now generates the thumbnail using libvips ImageProcessing::Vips  .source(image)  .resize_to_limit!(400, 400)

Other processors

In contrast to most file attachment libraries, file processing in Shrine is just a functional transformation, where you receive the source file on the input and return processed files on the output. This makes it easier to use custom processing tools and encourages building generic processors that can be reused outside of Shrine.

Here is an example of transcoding videos using the streamio-ffmpeg gem:

# Gemfile gem "streamio-ffmpeg"
class VideoUploader < Shrine  Attacher.derivatives do |original|  transcoded = Tempfile.new ["transcoded", ".mp4"]  screenshot = Tempfile.new ["screenshot", ".jpg"]   movie = FFMPEG::Movie.new(original.path)  movie.transcode(transcoded.path)  movie.screenshot(screenshot.path)   { transcoded: transcoded, screenshot: screenshot }  end end
movie.video_derivatives! # create derivatives  movie.video #=> #<Shrine::UploadedFile id="5a5cd0.mov" ...> movie.video(:transcoded) #=> #<Shrine::UploadedFile id="7481d6.mp4" ...> movie.video(:screenshot) #=> #<Shrine::UploadedFile id="8f3136.jpg" ...>

Metadata

Shrine automatically extracts metadata from each uploaded file, including derivatives like image thumbnails, and saves them into the database column. In addition to filename, filesize, and MIME type that are extracted by default, you can also extract image dimensions, or your own custom metadata.

class ImageUploader < Shrine  plugin :determine_mime_type # mime_type  plugin :store_dimensions # width & height   add_metadata :resolution do |io|  image = MiniMagick::Image.new(io.path)  image.resolution  end end
photo.image.metadata #=> # { # "size" => 42487494, # "filename" => "nature.jpg", # "mime_type" => "image/jpeg", # "width" => 600, # "height" => 400, # "resolution" => [72, 72], # ... # }

Validation

For file validations there are built-in validators, but you can also just use plain Ruby code:

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]   unless ImageProcessing::MiniMagick.valid_image?(file.download.path)  error << "seems to be corrupted"  end  end  end end

Backgrounding

In most file upload solutions, support for background processing was an afterthought, which resulted in complex and unreliable implementations. Shrine was designed with backgrounding feature in mind from day one. It is supported via the backgrounding plugin and can be used with any backgrounding library.

Shrine.plugin :backgrounding Shrine::Attacher.promote_block do  PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_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.create_derivatives # perform processing  attacher.atomic_promote  rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound  # attachment changes are detected for concurrency safety  end end

With Shrine, there is no need for a separate boolean column that indicates the processing status. Processed file data is stored into the attachment database column, which allows you to easily check whether a file has been processed.

photo = Photo.create(image: file) # background job is kicked off  photo.image(:large) #=> nil (thumbnails are still being processed) # ... sometime later ... photo.image(:large) #=> #<Shrine::UploadedFile> (processing has finished)

Direct Uploads

For client side uploads, Shrine adopts Uppy, a modern JavaScript file upload library. This gives the developer a lot more power in customizing the user experience compared to a custom JavaScript solution implemented by Refile and Active Storage.

Uppy supports direct uploads to AWS S3 or to a custom endpoint. It also supports resumable uploads, either directly to S3 or via the tus protocol. For the UI you can choose from various components, ranging from a simple status bar to a full-featured dashboard.

Shrine provides server side components for each type of upload. They are built on top of Rack, so that they can be used with any Ruby web framework.

UppyShrine
XHRUploadupload_endpoint
AwsS3presign_endpoint
AwsS3Multipartuppy-s3_multipart
Tustus-ruby-server