Active Storage File Upload Behind The Scenes

Liroy Leshed
Aug 20 · 4 min read

Active Storage is a framework in Ruby that makes it a breeze to upload files and reference them in the cloud (or a local disk). It’s built into Ruby On Rails 6, but it’s also got a JavaScript library. In fact, that’s what I like most about Rails. It’s got your back. It delivers complete packages out of the box. Packages that work well together. Client to server — it’s got everything you need! And it’s got beautiful code. Making it a pleasure to work with.

In this post, you will see how Active Storage really works from the inside. We will track the main flow of the program, and see how it processes a file uploaded by the user through the browser with JavaScript. And then, how the file is uploaded to a local disk with Ruby. Let’s get started.

Autostart

It all starts here, with autostart.This is the function that starts everything in the Active Storage world. It invokes the start() function (which we’ll look at shortly) if the ActiveStorage object is present in the browser’s window. Here it is:

function autostart() {  if (window.ActiveStorage) {     start() // Imported from ujs.js  }}

Then, Active Storage uses a setTimeout function to automatically call the autostart function after one millisecond:

setTimeout(autostart, 1)

Start

So what does start do? Looking at the start function, first it’s doing a check to make sure the application hasn’t already started, and if so, it makes a note of it by setting started to true. The next thing it does is to attach an event listener to the submit event with the handler didSubmitForm. That means that didSubmitForm will run every time the submit event is triggered by the user. The event will be triggered after the user has submitted the form with the file they chose to upload. That’s what it looks like:

export function start() {  if (!started) {    started = true    // ...    document.addEventListener("submit", didSubmitForm)  }}

Did Submit Form

Once a user has uploaded a file, Active Storage will run the handler didSubmitForm. Which in turn will pass the event to handleFormSubmissionEvent, and invoke (call) it.

function didSubmitForm(event) {  handleFormSubmissionEvent(event)}

Handle Form Submission Event

What’s the handleFormSubmissionEvent function all about? It’s creating an instance of the DirectUploadsController with the specific form the user has submitted. And it’s starting it with controller.start(). This controller’s API is such a delight!

function handleFormSubmissionEvent(event) {  // ...  const controller = new DirectUploadsController(form)

controller.start()
}

Direct Uploads Controller

In the Direct Uploads controller Active Storage creates a new instance of the DirectUpload JavaScript model, passing it the file the user is uploading, its url, and an instance of the controller class.

export class DirectUploadsController {  this.directUpload = new DirectUpload(this.file, this.url, this)}

Direct Upload

It’s in this model that the core of the process is taking place. It does two things: It creates a fresh new blob instance from the BlobRecord model, which saves the meta data of the file the user is uploading in the database without actually uploading the file to the disk. And then, it calls create() on the blob instance and passes it a callback. It’s in this callback that the actual file upload happens.

The way it does that is by creating a new upload instance from the BlobUpload model class passing it the blob in question.

export class DirectUpload {  create() {    // ...    const blob = new BlobRecord(this.file, checksum, this.url)    blob.create( =>      const upload = new BlobUpload(blob)    )  }}

Blob Record

The BlobRecord model actually creates an AJAX POST request to the server. From here the Rails controller in the back-end handles the request.

export class BlobRecord {  constructor {

// ...
this.xhr = new XMLHttpRequest this.xhr.open("POST", url, true) } create() { this.xhr.send() }}

Direct Uploads Controller

Active Storage has its own base controller class that inherits directly from ActionController::Base. It’s called ActiveStorage::BaseController, which just keeps things clean with one root controller for the back-end of the framework.

The controller calls create_before_direct_upload which just calls create, and creates a new instance of the ActiveStorage::Blob back-end model in Ruby. Here it’s essentially writing the meta data of the file uploaded to the database. The server returns some JSON with the url and the service headers for direct upload.

class ActiveStorage::DirectUploadsController <   ActiveStorage::BaseController  def create    blob = ActiveStorage::Blob.create_before_direct_upload!  (blob_args)    render json: direct_upload_json(blob)  end
end

Blob Upload

This is the second AJAX request it makes to the server. This time it’s a PUT request which will go directly to the DiskController in the back-end which in turn uploads the file to the service (in this case a local disk).

export class BlobUpload {  constructor {    this.xhr = new XMLHttpRequest    this.xhr.open("PUT", url, true)  }  create() {    this.xhr.send(this.file.slice())  }}

Disk Controller

Finally. That actual file upload to the disk happens here. The controller responds with an update action which will delegate to ActiveStorage::Blob and calls upload on the service. At this point the process is finalized and the file is present on the service.

class ActiveStorage::DiskController < ActiveStorage::BaseController  def update    ActiveStorage::Blob.service.upload  endend

Focusing on the main flow

Of course there’s a lot more to the Active Storage file upload process, and I simplified things a little bit. But I just wanted to get a feel of what the main flow of the program looks like. This framework is HIGHLY documented so I suggest you take it for a spin yourself. Enjoy!

Ruby Inside

Ruby articles and posts

Liroy Leshed

Written by

Founder & CEO @ http://squeezerhq.com

Ruby Inside

Ruby articles and posts

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade