Active Storage. Storing Files With a Rails API

jpstorrie

Josiah

Posted on April 1, 2023

Active Storage. Storing Files With a Rails API

Are you in the quite specific scenario of wanting to upload photos to a rails backend with a react front end but don't know how? I've got you covered with a step by step guide on working with active storage to store your audio, video, and photo files.

Let me just start this by saying that this tutorial may not cover complete best practice as I have been crunched on time and needed the quickest solutions to my problem. This blog is meant to help those of us who are newer to the world of code. With that out of the way, let's get started.

Firstly, I'm going to be using a rails api with a postgresql database and a react front end. I'll do this with the command
rails new project_name --database=postgresql --api

From inside the directory of your new project you can run your vite, create-react-app, or whatever other command you will be using to build your client side. Once your API has been built along with your client side starter, we can move on.

Now, you will want to ensure you have all your gems installed. First, uncomment the rack-cors gem, we'll get to why this is necessary later. Next we will need a gem that is not automatically given to us by ruby in newer versions, this is called active model serializers, be sure to add it to your gem file.

gem "rack-cors"
gem "active_model_serializers", "~> 0.10.13"
Enter fullscreen mode Exit fullscreen mode

Then run bundle install to ensure you have your gems installed. Make sure you have done this before you start your migrations or rails won't generate the serializer for each table you make(we need those).

From here you are going to want to create your database with rails db:create

Then install the rails storage method active storage. This can be done by running the command rails active_storage:install

This will add two migrations to your table. Now is where you would add the rest of your project tables, in this example case, a table for my user that will later have a profile photo. This table will have a name for the user to which we will add a picture later. I'll add this table to my database with the command rails g scaffold User name. From here we are good to migrate with. rails db:migrate.

Once you have migrated your tables here is where we will want to jump into the model(app/models/user.rb) for our user table and we will want to add the has_one_attached macro given to us by active storage.

class User < ApplicationRecord

   has_one_attached :image

   end
Enter fullscreen mode Exit fullscreen mode

In this scenario, image can be named whatever you would like it to be.

Next, we will hop over to our serializer for our user table(app/serializers/user_serializer.rb) and add our image attributes that we want in our table. The first step to this is including our rails app route url helper. This will help generate a url that links to our image. The first step is done like so

class UserSerializer < ActiveModel::Serializer
  include Rails.application.routes.url_helpers
  attributes :id, :name

end
Enter fullscreen mode Exit fullscreen mode

This should automatically generate with the attributes you created when you started. Next we will want to define the route to the image, this can be done by creating a method for defining the file you want to return and the method name should match the name given when we defined the has_one_attached macro. After this method has been defined, we add the method name as an attribute.

class UserSerializer < ActiveModel::Serializer
  include Rails.application.routes.url_helpers
  attributes :id, :name, :image

  def image
  rails_blob_path(object.image, only_path: true) if object.image.attached?
  end
end
Enter fullscreen mode Exit fullscreen mode

Just a few more steps before we move to front end. We need to change the params.permit in our user controller so head on over to app/controllers/user_controller and from there find the user_params method. That should look something like

def user_params
      params.require(:user).permit(:name)
    end
Enter fullscreen mode Exit fullscreen mode

What we need to do is add the image attribute that we added up above to our params like so...

def user_params
      params.require(:user).permit(:name, :image)
    end
Enter fullscreen mode Exit fullscreen mode

Let's finish up the work on the backend by enabling cors. We can do this by going to the config/initializers/cors.rb file. That file should look something like this...

# config/initializers/cors.rb
# Be sure to restart your server when you modify this file.

# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.

# Read more: https://github.com/cyu/rack-cors

# Rails.application.config.middleware.insert_before 0, Rack::Cors do
#   allow do
#     origins 'example.com'
#
#     resource '*',
#       headers: :any,
#       methods: [:get, :post, :put, :patch, :delete, :options, :head]
#   end
# end
Enter fullscreen mode Exit fullscreen mode

In that file, you will want to uncomment the 7th line(the one that starts with Rails.application) all the way down to the bottom of the file. Then you will want to change the 10th line. In the quotes where it says 'example.com', you will want to put your domain, commonly 'localhost:3000', alternatively, you can put a *, but this is not recommended. The file should look something like this

# config/initializers/cors.rb
# Be sure to restart your server when you modify this file.

# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.

# Read more: https://github.com/cyu/rack-cors

Rails.application.config.middleware.insert_before 0, Rack::Cors do
   allow do
     origins 'example.com'

     resource '*',
       headers: :any,
       methods: [:get, :post, :put, :patch, :delete, :options, :head]
   end
 end
Enter fullscreen mode Exit fullscreen mode

After all of that, we have finished the backend configuration! Let's move to React now.

For react, we will need to create our form to input our necessary fields. This can be done by binding the FormData constructor to the form element or it can be done using state. We only have a couple necessary elements so I'll go that route here. We'll first build our states, then build our return statement where we set our states. We'll also add an onSubmit to the form with a function that we'll build later.

const [name, setName]=useState(null)
const [image, setImage]=useState(null)

return( 
    <form onSubmit={handleSubmit}>
      <input type="text" 
      onChange={(e)=>setName(e.target.value)}/>
      <input type="file" 
      onChange={(e)=>setImage(e.target.files[0])}/>
    </form>
)
Enter fullscreen mode Exit fullscreen mode

Next lets build that handleSubmit function. To submit forms with active storage, we'll need a JavaScript constructor called FormData. FormData is required for sending files from a form. To do this we must first declare the constructor as a constant that we can call whatever we want, I'll call it formData. We can then append the necessary items to this FormData constructor. This append statement takes two arguments, the first being the key in the object you are wanting to send, and the second, the value you want to send. We can add this to a submit event and test what it outputs.

function handleSubmit(){
const formData = new FormData()
formData.append("name", name)
formData.append("image", image)
}
Enter fullscreen mode Exit fullscreen mode

After appending the necessary data to the form, we'll need to send our data to the backend. We'll build our post statement with formData as our body.

function handleSubmit(){
const formData = new FormData()
formData.append("name", name)
formData.append("image", image)

fetch("api/users", {
method: "POST",
body: formData
}).then({/*do something with your data here*/})
}
Enter fullscreen mode Exit fullscreen mode

There you have it, active record used simply with react! Leave any improvements or comments below so we can all program better!

💖 💪 🙅 🚩
jpstorrie
Josiah

Posted on April 1, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related