Shrine 2.0 Released

Shrine is a full-featured library for handling file uploads in Ruby applications. Main advantages of Shrine are good design, loads of flexibility for achieving maximum performance and best user experience for any use case, and advanced features like backgrounding, direct uploads, logging and more.

In this post I would like show you some of the most notable improvements since version 1.0.

Storages & Plugins

Apart from FileSystem and S3 storages that ship with Shrine, there are now Cloudinary, Imgix, Flickr, Fog, GridFS, Memory, and SQL storages that can be used with Shrine.

Apart from Sequel and ActiveRecord integrations that ship with Shrine, there are now integrations for Mongoid, Hanami and Reform as well.

Since Shrine has good abstractions, both storage classes and plugins are very easy to write. There are even guides for creating storages and creating plugins.

Upload options

Storages like S3 and Cloudinary support a variety of options when uploading files. With S3 you can choose public/private, expiration, caching, while with Cloudinary you can choose responsive breakpoints, transformations, face detection and other.

The simplest way to set upload options is directly on the storage:

Shrine::Storage::Cloudinary.new(
  upload_options: {
    responsive_breakpoints: {
      min_width: 200,
      max_width: 1000,
    }
  },
  **options
)

Shrine also ships with upload_options plugin which allows you to set upload options dynamically:

plugin :upload_options, store: ->(io, context) do
  if context[:version] == :original
    {acl: "private"} # the original file is private
  else
    {acl: "public-read"}
  end
end

Metadata

With Shrine it’s really easy to extract metadata of uploaded files; by default Shrine extracts filesize, original filename and MIME type, and saves it to the <attachment>_data column as JSON.

Shrine also has determine_mime_type plugin for determining MIME type from file contents, and store_dimensions plugin for extracting image dimensions. From Shrine 2.0 you are now able to combine built-in analyzers:

plugin :determine_mime_type, analyzer: ->(io, analyzers) do
  analyzers[:mimemagic].call(io) || analyzers[:file].call(io)
end

The above first attempts to determine file’s MIME type with MimeMagic, and falls back to the file command.

Download endpoint

If you’re storing files in an SQL or Mongo database, or you’re caching files in the tmp/ directory because Heroku doesn’t allow you to write into the public/ directory, your files won’t be accessible via URL.

Or you may want that all uploaded file URLs go through your application (regardless of where they’re stored), so that you can require authentication for those files.

To cover both scenarios Shrine now has the download_endpoint plugin. This plugin provides a Rack endpoint which you can mount inside your application, which streams uploaded files. It’s even smart enough to set the “Content-Length” response header, so that browsers can show an ETA.

class VideoUploader < Shrine
  plugin :download_endpoint, storages: [:store], prefix: "videos"
end
Rails.application.routes.draw do
  mount VideoUploader::DownloadEndpoint, to: "/videos"
end

If your files are stored on a remote storage like S3, the endpoint will stream the file as it is being downloaded. I’ve tested this with a video uploaded to S3, and through this enpdoint I could start watching it already after 3 seconds, even though most of the video hasn’t yet been downloaded from S3.

Backups

You may want the uploaded files to be automatically backed up. Some cloud services have this feature built-in, but some don’t. For that reason Shrine provides a generic backup plugin which automatically backs up files uploaded to the main storage, to any other Shrine storage.

storages[:s3_backup] = Shrine::Storage::S3.new(bucket: "myapp-backup", **options)
plugin :backup, storage: :s3_backup

Callbacks

Sometimes you may want to do additional actions when attachment is cached (uploaded to temporary storage) or stored (uploaded to permanent storage). Shrine now provides Attacher#cached? and Attacher#stored? methods which you can use in callbacks:

class Document < Sequel::Model
  include FileUploader::Attachment.new(:file)

  def before_save
    super
    if column_changed?(:file_data)
      if file_attacher.cached?
        # ...
      end

      if file_attacher.stored?
        # ...
      end
    end
  end
end

Testing

Shrine provides a “direct upload” feature, which allows you to asynchronously start caching the file as soon as the user chooses it in the form. For S3 storage you can upload the file directly to Amazon, and these are called “presigned uploads”.

If you were writing acceptance tests around presigned uploads, and you wanted files to be uploaded to FileSystem rather than S3 in order to avoid HTTP requests in tests, you needed to create special “test” conditionals in your application code.

As of Shrine 1.4.0, you don’t need to make any additional setup when testing presigns, using FileSystem will just work.

Switching

If you ever decide you want to switch to Shrine from an existing file upload library, I wrote guides for CarrierWave, Paperclip and Refile users. The guides explain some of the key differences in how Shrine works compared to the other libraries, following with a detailed 1-1 mapping of features, and complete instructions on how to migrate a production app to Shrine.

Future

While you don’t need any Shrine-specific gems to process files in Shrine, the image_processing gem is a convenient collection of high-level macros for common image processing requirements. Currently it only supports MiniMagick, but I would like to add support for VIPS, and perhaps RMagick since it’s now revived.

I also plan to create Shrine integrations for other on-the-fly processing services like FileStack and Uploadcare.

Conclusion

I’mv very happy with the way Shrine is going, I think it’s successfully addressing the limitations of existing file upload libraries, and provides some nice unique features.

Janko Marohnić

Janko Marohnić

A passionate Ruby backend developer who fell in love with Roda & Sequel, and told Rails “it’s not me, it’s you”. He enjoys working with JSON APIs and SQL databases, while prioritizing testing, and always tries to find the best library for the job. Creator of Shrine and test.vim.

comments powered by Disqus