Thinking of a classic Rails app with user uploads, the usual workflow is: upload user file to the controller, process the upload with gem like CarrierWave or Paperclip, upload it to some cloud storage and save the model with a reference to the storage.
It’s a proven solution especially when you want to validate, resize or somehow process the file — for example user avatar. But what if the file is just a file, and it doesn’t need to be processed by the backend? Attachable CV in PDF is a good example of such case. By using Amazon S3 direct uploads, you can avoid extra load on you application servers with file uploads, and serve more requests instead.
Instead of uploading a file into Rails controller, AWS S3 allows to presign a unique upload URL, and then the user submits the form with file directly to that Amazon URL. In the database, we can just store address of the file on S3, like https://s3-eu-west-1.amazonaws.com/mybucket/myfile.pdf.
There are few other blog posts about mastering Rails and S3 Direct uploads, but in my post I want to focus on “vanila” solution, without using any gems like CarrierWave, Paperclip or s3_direct_upload.
Assuming we have Rails 4.2 app, let’s start by creating the User model with avatar field:
To use Amazon S3 API, let’s add the official AWS gem, aws-sdk-v2 to the Gemfile:
User upload will be sent to the presigned S3 URL, and this URL will be valid only for single upload. In this case, user won’t be able to upload more files then we allow and to pollute the S3 bucket. You can also limit the maximum allowed file size to upload.
We will need controller action to presign the request:
This controller action will accept filename parameter like selfie.jpg and generate the presigned URL for exactly this filename.
Let’s write the UploadPresigner class to work with S3:
Basically, this class takes a filename, makes a request to AWS S3 API and returns presigned URL with token, which we will use on the client side.
Please notice that we use ENV variables to store AWS access and secret keys, don’t forget to obtain them! It’s also important to create the S3 bucket before uploading files (replace the bucket name in the code) and to configure it.
Now, let’s configure the S3 bucket to receive uploads from the browser.
Open AWS Console, go to the bucket properties, open “Permissions” and click on “Add CORS configuration”. Then, paste this XML snippet:
These settings are required to process direct uploads to S3 and they are recommended by AWS.
Now when your bucket is able to accept browser uploads, and it’s time to prepare the form.
Now when the page is ready, start rails server and open the /users/new in your browser.
Enter some name and choose an avatar. After you submit the form, controller will redirect you to the show action. Let’s improve it a bit to display the link to attachment:
Now all user uploads in your app are processed directly to AWS S3, without causing any extra load on the backend.
By using direct uploads in the customer’s app, I reduced the file storage codebase and avoided using some unnecessary gems.
There are however situations when you can’t use direct uploads: for example, if you want to resize or somehow validate the user upload.
As the next step, you can add the code for cleanup: after the record is destroyed, the app should destroy remote file on S3 storage.
Tweet the link
About the author
Kir Shatrov helps businesses to grow by scaling the infrastructure. He likes to write about software, scalability and interesting stories that he runs into at work. Follow him on Twitter to get the latest updates: @kirshatrov.