Active Storage. Storing Files With a Rails API
Tags: webdev, ruby, rails, react
Published: 2023-04-01 18:57:34

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"
```

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
```
 
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
```

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
```

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
```

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
```

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
```

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
```

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>
)
```


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)
}
```

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*/})
}
```

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