Josiah
Posted on April 1, 2023
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!
Posted on April 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.