Active Storage with Rails API
By: Myung Kim
Tags: React, Web Development, Active Storage, Ruby on Rails, Rails Api

Using Active Storage in a pure rails app is pretty straightforward but, when we switch over to a Rails API with a detached front-end (such as a React app), it becomes a bit more tricky. There are a lot of articles and blogs floating around about Active Storage in a pure rails app or exclusively how to utilize Active Storage on a detached front-end.Therefore, this article will try to be a comprehensive, quick start guide to setting up Active Storage in the back-end and sending data from a detached front-end that Active Storage can read.

From the Back-End

Let’s start with setting up Active Storage in the back-end and connecting the models that should have files attached to them. Start by installing Active Storage via the command line:

rails active_storage:install

Running this command will create migration files that will generate an active_storage_blobs and an active_storage_attachments tables. For more detail on what these tables are doing, feel free to read Rails’ documentation for Active Storage. Run these migrations and now these tables should be in your schema.

Now it is time to connect these tables to the models you want the uploaded files to belong to. This is conveniently done through the built-in Active Record methods of has_one_attached or has_many_attached based on whether an instance should have a single or many attached files. These methods take in an argument of a string or symbol which will act as the reference/key to the getting and setting of the instance’s file. Think of it as establishing another column in the model’s table, except it will be storing it in the Active Storage tables rather than the model’s table. So it will ultimately look something like this inside of a hypothetical User model:

class User < ApplicationRecord has_one_attached :photo

end

Finally, we can work on the controller part of the process. As mentioned earlier, Active Storage files attached to a model can be thought of as just another attribute of that model. Therefore, as long as you have the key/reference to that file (declared via the has_one_attached method, photo in the case above), you can simply place that in your strong parameters as you would with any other normal attribute. Active Record magic will know to look for the attachment when it encounters this key.

From the Front-End

The Front-End can be tricky. There are a few specific key points you should know when trying to send a file’s data to the back-end for Active Storage to interpret. First and foremost, let’s assume you have a working form (in React or vanilla JavaScript) that properly submits to the correct back-end controller. This form should have an input of type “file” for a user to have the ability to upload a file from their local machine. This is what the input tag should look like:

<Input type='file' name='photo'/>

It is important to note that, in my example, I defined name as ‘photo’ based off of my previous examples, and what key I am looking for in my strong params. With this, the first important detail to note is that in-order to access the value of the file input field you must use .files[0] instead of .value . Dot value will return a strange ‘fake url path’ that isn’t very useful. We also indicate at index zero because dot files returns a collection of information on the uploaded file; we really want the file data at index zero. This information will become useful as we talk about the most important step of this process: FormData.

Usually, we would send a form’s data to the back-end via an object of key and value pairs of the inputs (their names as the keys and values as, well, the values), JSON.stringifying this, and sending it as the body in our request. However, it isn’t as simple as this when we are working with files because they are not convertible to strings or formatted for JSON. Therefore, instead of sending it as JSON, we send it as a FormData object. You can read up more on the intricacies in the MDN Web Docs linked in the resources, something to do with encoding the data as "multipart/form-data" .

If the only thing you are sending within that form is the file, then utilizing FormData can be as simple as new FormData(formObj) where ‘formObj’ is the selected form element. However, if you are sending other information along with the file, such as a username of type string, then you can utilize FormData’s append method like so:

const data = new FormData()

Object.keys(formObj).forEach((key, value) => {

data.append((key, formObj[key])

}

This is essentially iterating through a ‘formObj’ with key and value pairs of the inputs; however, it is now time to utilize the dot files I mentioned earlier when trying to store the value for the file input. For example, in a React app, this would be storing the input’s .files[0] into the state on change. Then, sending the state as the ‘formObj’ to be used in the above snippet of code. Additionally, this FormData logic would most likely be placed wherever and right before the request to the back-end.

Finally, the FormData object should be ready to send to the back-end. But wait, it’s still not working! The last step necessary is to make sure that headers don’t include ‘application/json’, that you are not JSON.stringifying it, or doing anything with JSON because, remember, we are not sending JSON (some ‘multipart/form-data’ stuff). Now, you’re back-end should be able to receive the file data, attach it to the newly created or updated instance, and you should see the file appear wherever you’ve set your storage.

Phew! That was a lot! Now, there are a few things left to check off. If you are wondering how to display, get or send to the front-end the file that is now in your storage, you can add the following line of code in your Serializer to point to the file location in your back-end:

One other important issue I will tackle in a future blog is where to store the files. This blog is under the impression that your storage.yml is pointing to your local storage, but it can point to more practical, cloud based storages such as S3 and Azure. All in all, thank you for reading my long winded blog and hoped it helped solve any issues with Active Storage in a headless Rails API.

Resources

Rails’ official guide for Active Storage: https://edgeguides.rubyonrails.org/active_storage_overview.html

More information on FormData:

https://developer.mozilla.org/en-US/docs/Web/API/FormData

Other blogs I’ve found helpful on this matter:

https://medium.com/@anurag_722/send-files-to-rest-api-with-rails-5-2-active-storage-as-backend-65128a1170b

https://tech.xing.com/using-ruby-on-rails-5-2-active-storage-598982e99fb1