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

Yatr: Our hack for the Techcrunch NYC Hackathon

This weekend Gabo, Rob and I participated at the Techcrunch Disrupt NYC Hackathon, a 24 hour coding event where you come out the other side with some product. We decided to make a comments/conversation engine with some interesting twists named Yatr.

Why Comments?

It took us quite a while to reach a decision (actually till about 30 mins before the event) on what we would build. Comments are interesting for a couple of reasons.

  • Comments are really important but mostly neglected in the social world. If you look at the apps space, there are a million “link farming” apps that grab the top stories from your Twitter stream and yet there arent much that bring out the conversations around them.
  • A lot of blogs disallow comments completely (usually citing low signal to noise ratio).
  • Conversations often tend to get too specific: For example, a conversations around “iPhone4” are interesting to people who have Android phones. It would be great to surface these products up into a “phones” conversation. Even better, I’d like to subscribe to the top conversations around phones
  • I had been reading a lot of posts like this: https://drumbeat.org/en-US/challenges/beyond-comment-threads/
  • We wanted to blur the line between comments and content (blog posts). Yatr comments were supposed to be embeddable and a subaccount could be used as a blog engine on its own.

The Hack:

Our hack (video below), involved a bookmarklet that allows you to comment on any link. This comment can be shared to Twitter but also gets added to our database. Additionally we used a Readability library to find the relevant content on the page and then a term extraction library to find the main words in the post to try to find the appropriate tags (additionally I would like to use a clustering algorithm to improve the context recognition system). When a comment is made, we add that to all the relevant topics we can find.

On the other side, we created unique pages for each of the items commented upon, both the links specifically and also the tags on the content. The pages get pretty interesting where we Embed.ly ( and in the future DuckDuckGo‘s zero click API) to add more information to the page. The video below for example takes you from the link about “Portal 2” to the tag page with a bunch of popular links around Portal 2.

The whole App was written in Sinatra. I am a Ruby noob so I had been looking for a project to play with some middleware/Ruby. A big part of my goal for the hackathon was to learn using a new technology and I definitely came out of that pretty educated.

We had a lot of ideas on what this could/should be (actually all 3 of us had different ideas on what we wanted it to be, it didn’t help that all of us have very different opinions on social networking 😉 ). I am hoping to fix a bunch of things before putting it online, even if just for kicks.

P.S: Also check out Rob’s post on the event. He has photos and stuff!