Using ActiveStorage with DropZone in a React app

The last year or so I have been working on an app for teaching programming that I hope to release in a few weeks. The CMS for the app is a React based web app powered by a Rails server. The Rails server is running in an API-only mode which has been working out reasonably well.

One of the recent additions to Rails has been the ActiveStorage system that purports to make attaching media to Rails models really simple. Unfortunately most of the documentation around it involves using Rails’ View tier as well which the API-only mode strips out. In fact, getting there is an open bug on ActiveStorage to actually make it work out of the box in API-only projects.

Oh and one more thing: ActiveStorage doesn’t support uploading to multiple buckets, just in case thats a dealbreaker for you.

After a couple of days of struggling through it, I finally have it working in an API only mode using the React Dropzone Component instead of a boring filepicker.

Also note, there is an open source React component for interacting with ActiveStorage which I did not try, mostly cause I was already halfway done with the Dropzone implementation by the time I discovered it.

Issue #1: ActiveStorage CSRF errors in API only mode

ActiveStorage ships with a JavaScript library that does a lot of the work, but one of the issues with using ActiveStorage from a react app was that it tries to do CSRF token validation on the requests but cannot. To solve it, I added a initializer in the initializers folder that skipped CSRF checks for it

ActiveStorage::DirectUploadsController.instance_eval { skip_forgery_protection }

Issue #2: Cors challenges

So this isn’t a bug but when working on a React app using the CreateReactApp project, the React app runs on port 3000 while the Rails server runs on port 3001. To prevent CORS issues when uploading images, I had to add the Ract::Cors gem and a cors initializer that allowed requests from localhost:3000 during development

Rails.application.config.middleware.insert_before 0, Rack::Cors do
 allow do
   origins 'localhost:3000'
   resource '*',
    headers: :any,
    methods: [:get, :post, :put, :patch, :delete, :options, :head]
 end
end

Also, since the React app was using DirectUpload to S3, you have to add the right Cors settings for your S3 buckets as well (this blog post helped figure out the CORS issue)

Uploading an Image using React and Dropzone:

The core part of the image uploader component is below:

const url = '/rails/active_storage/direct_uploads'
const upload = new DirectUpload(file, url, this)

upload.create((error, blob) => {
 if (error) {
    console.log("Image Error:", error)
 }
 else {
    this.dropzone.removeAllFiles(true);
    this.props.onImageUploaded(blob)
   }
})

The DirectUpload class comes from the ActiveStorage JavaScript file. When this code is run, the image is uploaded to the storage provider and you get back a blob (just a JavaScript hash)  with a bunch of metadata (that is saved in the `active_storage_blobs` database table.

One of the keys in the hash is called signed_id. To assign the uploaded image as an attachment to any of the models, the model has to have a has_one_attached or a has_many_attached field. In my case its something like

class Book < ApplicationRecord
  has_many_attached :images
end

In my book controller I can then accept a PUT request that updates the record like so:

@book.images.attach( params[:signed_id] )

or better yet just let strong_params do it automatically.

[Update] Another good post on using pre-signed POST requests for S3 Uploads

Author: Arpit Mathur

Arpit Mathur is a Principal Software Engineer at Comcast Labs where he is currently working on Virtual and Augmented Reality as well as investigating Blockchains and the decentralized internet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s