Rails: Uploading images confidently with Shrine.rb
Upload images using Shrine.rb and Dropzone.js.
Shrine is file uploading library written in Ruby, it’s compatible with plain ol’ Ruby, Rails, Hanami, and any other Rack-based web framework.
You should use Shrine if you are starting a new project or want to upgrade your existing solution to use something more flexible than carrierwave, paperclip or refile. Shrine is a library for file uploading and provides tools to build plugins on top of. Because of this, almost all of Shrine’s configuration is done through plugins.
A file upload form can easily become an attack vector. Shrine incorporates best practices and patterns for securely uploading files and images. Janko Marohnić is the creator of Shrine and cares a lot of improving the current state of file uploading in Ruby. Check out his blog and the Shrine documentation for more in depth knowledge about file uploading.
To begin, let’s out line what our requirements for image uploading are:
Upload images to Amazon S3
Image versioning (sm, mg, lg, etc…)
Ensure image files are cleaned up
Upload in background
Validate size and filetype
Remove attached image
Cache image file on errors
Drag and drop upload
Access images through CloudFront CDN
Our requirements are fairly modest, so lets start by installing the gems well need to use. Anytime you install a plugin with Shrine it’s a good idea to read the full documenation for it to learn about usage and gem dependencies.
Next, lets create an initializer called config/initializers/shrine.rb. This file will be responsible for configurating plugins and background jobs.
The backgrounding plugin exposes the promote and delete methods where we can pass a sidekiq worker. We define these workers in app/jobs.
Versioned files are processed in a promote job, if your sidekiq server is not running, then versioned images will not work. By default the original file will be used for a missing version until it becomes available.
You can use the plugin :recache to make some version available immediately and process others in the background.
With the initializer setup we are ready to create an uploader class which inherits from Shrine. This uploader class will be responsible for encasuplating requirements for uploading files. For this example we will create a generic image uploader class that can be applied to most models.
This generic uploader seems to be doing a lot of work, but remember that Shrine is a library which encourages the use of plugins to drive its functionality. As of right now, we have a fairly lightweight uploader that fulfills all of our requirements.
If you would like to use rails-assets to require Dropzone, then place the following block in your Gemfile.
Or head over to dropzone.com for alternative installation solutions.
Setting up Dropzone is fairly simple, we’ll use data attributes to tell Dropzone what controller endpoint it should use.
We will also need to pass in the X-CSRF-Token request header for Rails which we can grab from the meta tag. You can also use a skip action filter to disable it, but I prefer not to.
Out of habit/convention, I’ve named all the div’s and id’s after the model name (picture). We’ll refer to the them in the view later.
This next part will dive into the code required to make Shrine work inside a Rails environment and reuse partials inside the controller.
Shrine’s code is mostly reusable across different frameworks and projects. If you are interested in an Hanami example, let me know!
Shrine looks for a <attribute>_data column when an uploader is attached. Knowing this we can generate a model to attach an uploader to.
In our model, we pass in the <attribute> name when attaching the uploader.
Lets define some routes to display and create pictures.
The index route will be display the uploaded image and provide a drag an drop interface to upload images.
The create action will be the endpoint for Dropzone. Dropzone will hit the endpoint for each file. So our create action can return the uploaded image.
This is the meat of the entire uploader and it really shows how amazing it is work with Rails, Shrine and Dropzone all together.
Just like with image uploader, let’s outline our requirements for creating the view:
View all uploaded images in a grid.
A form/dropzone for uploading new images.
Automatically append uploaded images to the grid.
All we need to make this happen is one partial to render our picture. Let’s begin by outlining the required HTML for pictures/index.html.erb which will render our partial.
This view is responsible for defining the div’s for Dropzone to consume, and rendering the @pictures collection. I’ve also enabled a preview template for Dropzone to display while the image is uploading.
Next up is the pictures/_picture partial.
With our picture partial in place, our uploader is now complete.
Let’s recap whats going on:
A user just dropped multiple files into the designated dropzone interface.
Dropzone hits the endpoint for each file the user dropped.
Rails created a new picture object, passing in the param from Dropzone.
Shrine automatically handles the data correctly.
If the picture object is created successfully then Rails renders the picture partial as an html string and return inside a JSON object.
When Dropzone receives a response back from Rails it reads the JSON object and appends the html string inside the DOM.