Temporary Image Uploads using Paperclip and Dropzone

V Vasilev
Turn-by-Turn
Published in
4 min readFeb 15, 2018

Paperclip makes it very easy to manage file attachments in Rails, but at the same time it isn’t a one-fits-all solution. There are certain capabilities that Paperclip doesn’t support, and one such example is re-rendering an image attachment which fails to save due to validation errors. The maintainers of the library have stated that such functionality is outside the purpose of the library, so we took on the path of providing a concise solution to the problem we were facing.

During brainstorming, we considered the following approaches:

Replace Paperclip

We did not have the available resources to replace Paperclip with Carrierwave, or any other similar library, given that we have a large codebase, and Paperclip is heavily used across different layers of our application. Replacing would have meant an extensive amount of code rewrites and refactoring which could have consequently lead to bugs and unexpected edge-case faults and fixes. We also didn’t find an imminent need to replace it, because it had so far performed its capabilities well and without much hassle.

Monkey-patch Paperclip

We managed to find examples (paperclip-keeponvalidation) of other developers monkey-patching core classes of Paperclip and ActiveRecord to make them persist an attachment if its ‘hosting’ model record fails validation. We at Wellbe prefer to avoid monkey-patches at all costs; one strong argument against the practice is that you’d be fiddling with core functionality and your changes may have unintended consequences in other parts of your application, and even with rigorous and extensive tests and Quality Assurance processes in place, you may not catch it before it’s out in the wild.

Replicate the model’s validation logic on the client side

It seemed like a good idea at first to not allow the form to be submitted before entered data was valid but that, however, is work-intensive depending on the extent and complexity of your validation logic. It goes without saying that there’s validation expectations enforced on the image file itself as well. Recreating all this logic on the client side for all models that have attachments would have proven cumbersome and still would not have solved the core issue — we wanted to re-render the image if validation of other attributes failed. Client side validation simply avoided it.

Enter the Dropzone.js

The solution was simple — in order to fill the gap we simply needed to build what was missing. As I mentioned before, the image files are also validated as, for example, we don’t allow files larger than 5 MB, only image MIME types are allowed, etc. Having had prior experience with Dropzone.js, it has many goodies to offer in a lightweight library: it will perform all of the aforementioned file validations and won’t allow the files to be uploaded, it offers both click-to-select and drag-and-drop functionality, it submits the file to a separate endpoint, it’s easy to configure, it exposes a clean API, and, lastly, it’s easy to use.

We use bower-rails so adding dropzone was trivial:

Divide and Conquer

The next piece of the puzzle was to separate the initial image upload into its own dedicated model which would only contain the attachment. We decided to call it TempImage:

Next up we created the controller that will process new temporary images sent by Dropzone:

For the purposes of this article, the model that these temporary images end up in is called User. The purpose of a temp image is to exist as its own entity but only for a short amount of time, after which it will be moved to a User record.

Gluing the pieces

The above code eventually ended up in a concern because we use this functionality in other models, but in essence it holds onto the id of a temp_image in a temp_avatar_id attribute. The temp_avatar_id attribute originates from Dropzone's save operation and is set in UsersController by form submission, which we will examine below. Before a record is saved, the temp avatar's image attribute is assigned to the avatar attribute. This simply tells Paperclip to copy the attachment of one model to another. After committing an INSERT or UPDATE operation, a temp_image that the User instance is keeping reference to is destroyed along with the actual attachment it holds.

Making the front-end work

At this point we know that we’ll need to permit a temp_avatar_id parameter in our users controller

Next off we add a hidden field in our users form that holds the id of the TempImage we create using dropzone:

Notice the attributes data-url="<%= temp_images_path %>" on the field container and data: { hidden_temp_field: true } on the hidden field. We'll need these for our javascript code which will initialize a dropzone instance.

With all the code in place, when we click on the dropzone selection element, or drag-and-drop an image into it, dropzone sends the file to our temp_images_path. In turn, the request sends back the id of the temp image that was saved. We use that as our temp_avatar_id in the user form. After we submit it, the User model copies the temp image into its avatar attribute and destroys the temp image. The benefit of doing all this is to ensure that when you attempt to upload an avatar for a user but other attributes don't pass the validations, the image you uploaded displays and persists the next time you submit the form.

--

--