Using Active Storage in Ruby on Rails Api
Published: 2023-02-16 21:33:42

Hi, if your building a project like I am and want to use Active Storage to handle file upload then you've come to the right place. 

First step is to setup your environment by creating a new rails application. I used postgresql for my database. 

Now you will want to create and migrate your model and controller for whatever table you want to have files for. Let's use User as our model.

install the Active Storage gem

```
rails active_storage:install
```

This will create a migration will 3 tables: active storage blobs, active storage attachments, active storage variant records.

create your schema by running:

```
rails db:migrate
```

Now you will want to create an association(macro) to your User table. 

```
class User < ApplicationRecord
  has_one_attached :image
end
```

We are going to make the attachment an Image, you can name it whatever kind of file it's going to be, we will reference it in our UsersController.

Next, you will want to create the params for the User in the UsersController. 

```
def user_params
  params.require(:user).permit(:name, :image)
end
```

In your gem , you'll want to uncomment out
 

```
# gem 'rack-cors' 
```

and run a bundle install. This will add it to your gemfile.lock

```
rack-cors (1.1.1)
rack (>= 2.0.0)
```
 Go to config/initializers/cors.rb and you'll see a new file added:

```

# 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
```
Uncomment out everything after 'Read more: https://github.com/cyu/rack-cors'. If your using a react app you'll want to replace origins will your react app server

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

```
 Rails.application.config.middleware.insert_before 0, Rack::Cors do
   allow do
     origins 'http://localhost:3000'
```
Make sure you input the correct server you using or you will get an error.


```
Rails.application.routes.draw do
resources :users, only: [:create]
end
```

```
class UsersController < ApplicationController
  def create
        user = User.create(user_params)
        if user.valid?
            user.save
            render json: user, status: :created
        else
            render json: { errors: ['User not valid']}, status: 422
        end
    end
end
```

On your Frontend you'll want to create an form that that send a POST to your backend route. I'm using Material UI for styling.

I created a SignUpForm component for the form and useState for our Input fields. For our file input field we specify the input type for our file. In Material UI, we use inputProps that. 

We add an on change function that uses an anonymous function that take in an event(e) and calls the setter function from our useState. 



```
import React, { useState } from "react";
import { TextField, Input, Typography, Button } from "@mui/material";

Function SignupForm(){
const [image, setImage] = useState([])
[name, setName] = useState("")


return (
 <form>
   <Typography> Name </Typography>
        <TextField
          label="Name"
          value={name}
          onChange={(event) => 
          setName(event.target.value)}
          
        />
        <br />
        <Input
          label="Image"
          type="file"          
          inputProps={{ accept: "image/*" }}
          onChange={(event) => 
          setImage(event.target.files[0])}
          name="image"
        />

        <br />
        <Button color="primary" type="submit">
          Submit
        </Button>
 </form>
)
}
export default SignUpForm
```
Notice that our onChange for Name and Image are different. When were selecting a file, we can typically select multiple files. We use "event.target.files[0]" because the files are in an array and we just want the first so we add an index of 0. This means we also have to set our initial state of image to an array.

We will add a function to the form that sends a POST request to our end point in our Backend. Because we have a file we have to send, we can't send a normal object. Instead We'll send a FormData object that we'll construct in the function. 

```
function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData();
    data.append("user[image]", image);    
    data.append("user[name]", name);
    
         
    fetch("http://localhost:3000/users", {
      method: "POST",
      body: data,
    })
}
```

We structure our FormData object in a manner that ruby accepts and send it to the backend without stringifying it. Add the handle submit to the form and test it out.

If open up the console, you will see that the new user has an image at attached.

Thank you!


{% embed https://guides.rubyonrails.org/active_storage_overview.html %}