DEV Community

Cover image for Uploading Profile Pictures in a React and Rails API App Part II
Rachel Williams
Rachel Williams

Posted on

Uploading Profile Pictures in a React and Rails API App Part II

Intro

This is the second part in my series on uploading images in a React application. If you haven't read the first part, here it is. This blog will pick up where the last one left off.

Setting Up Rails to Receive the Image

Obviously, the first thing I did was set up my route to send my POST request to. I decided have this route go to my users controller, since a picture is associated with a user. I went with '/users/:id/upload_photo' where id is the user's id.

Next up was creating the controller action. This blog largely follows the work of this blog. It's a really great resource if you would like to set this functionality up yourself. Here is the code for my uploadPhoto action:

def uploadPhoto
  //grabbing user from the db using the id from the query string parameters
  //i used strong params
  @user = User.find(profile_picture_params[:id])

  @user.profile_picture.attach(profile_picture_params[:profile_picture])

  if @user.profile_picture.attached?
    profile_picture_serializer = ProfilePictureSerializer.new(profile_picture: @user.profile_picture, user: @user)
    render json: profile_picture_serializer.serialize_new_profile_picture()
  else
    render json: {errors: "No profile picture attached"}, status: 400
  end
end

So above, I am grabbing the current user based on the id from the query string parameter. I used a strong params function, hence the profile_picture_params method.

ActiveStorage

In order to attach the image file to my user, I used ActiveStorage. From the Rails guide:

"Active Storage facilitates uploading files to a cloud storage service like Amazon S3, Google Cloud Storage, or Microsoft Azure Storage and attaching those files to Active Record objects. It comes with a local disk-based service for development and testing and supports mirroring files to subordinate services for backups and migrations."

ActiveStorage is already integrated into Rails, however to use it you need to create some migrations to make two tables in your database, active_storage_blobs and active_storage_attachments. You might recognize that word blob from part one of this blog series. To set up ActiveStorage I recommend following the blog I linked earlier or the ActiveStorage Rails guide linked above. You basically just run an install command and then run migrate to make the new tables.

Back to the Code

The profile_picture attribute on my user comes from this ActiveStorage macro, has_one_attached :profile_picture. From the Rails guides, this macro, "sets up a one-to-one mapping between records and files. Each record can have one file attached to it." Essentially, each of my users will have one image file attached to their record. Next up, I used the attach method from ActiveStorage to attach the image to the user's record.

ActiveStorage has a method attached? that checks if the user has a profile_picture attached to the record. If this is the case, the profile picture data and will be serialized and rendered as JSON. Here is the code for my Profile Picture Serializer:

class ProfilePictureSerializer

  def initialize(profile_picture: nil, user:)
    @profile_picture = profile_picture
    @user = user
  end

  def serialize_new_profile_picture()
    serialized_new_profile_picture = serialize_profile_picture(@profile_picture, @user)
    serialized_new_profile_picture.to_json()
  end

  private

  def serialize_profile_picture(profile_picture, user)
    {
      profile_picture: {
        user_id: user.id,
        image_url: user.get_profile_picture_url(),
        created_at: profile_picture.created_at
      }
    }
  end

end

Once again, this largely follows the setup from this blog. I wanted the JSON for the profile picture to contain the user id, image url, and date the record was created. The get_profile_picture_url() method comes from my user model. This method uses Rails' url_for() method to get the url for the profile_picture attached to the record. url_for() is a helper method and you will need to add include Rails.application.routes.url_helpers to the beginning of your model code in order to use it.

Configuring Cloudinary

To set up Cloudinary the first thing you will need to do is create a free account. This part is pretty straightforward so I won't go over it, but once that is complete you will need your key and secret which you can find on your dashboard when you are logged into your account.

Next up add the cloudinary gem to your Gemfile and run bundle install. Then add this to your storage.yml file in your config folder:

cloudinary:
  service: Cloudinary

Next step is to set config.active_storage.service = :cloudinary in your config/environments/development.rb and production.rb files. This tells active_storage to use Cloudinary to store the files. Then download the YML file from your Cloudinary account. You will find the link for this in the top right of your account details on your dashboard. Put this file in your config folder and be sure to add this file to your .gitignore because you don't want to push these credentials to Github.

As the blog mentions, "With this in place, ActiveStorage will automatically upload and retrieve images from the cloud."

A Minor Bug

As the blog I followed mentioned, I did receive this error the first time I tried to successfully post an image to my Rails API: ArgumentError (Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true). I followed what the blog said and added Rails.application.routes.default_url_options = { host: "http://localhost:3001" } to my config/environments/development.rb folder. I have 3001 as my port instead of 3000 which is the default Rails port.

Success

After adding the default url options, the JSON was fetched successfully! When I checked my Cloudinary account, I could see in my assets that my image was uploaded successfully. Also yes, this is a picture of my dog:

Profile Picture

I won't go over the details of how I rendered the image in my React app in this blog, but here is the final product. It needs some styling, but I was excited to have it show up!

React Profile Page

Another cool thing to note, is that if I change the profile picture, the old one will be automatically deleted from Cloudinary and replaced with the new one. I'm assuming this comes from the fact that I used the has_one_attached macro, so only one file can be associated with the record at a time.

Thank you for reading and let me know if you have any feedback or questions. Here is the repo for my Rails API if you would like to look at any of the code further.

Top comments (2)

Collapse
 
aquasar profile image
Alex Quasar

Just wondering about the reason for using Rails with React? What is the benefits or advantages of using Rails API vs something like Node/Express/Mongo, or Amplify

Collapse
 
racheladaw profile image
Rachel Williams

Hi Alex, thanks for reading! I used Rails for my project because that is the framework I learned during my bootcamp and the one we were supposed to use for our final project. However, I am currently starting to learn Node. I don't really know all of the benefits, but after some research it looks like the pros of Node are that it has newer and better maintained libraries, it's faster than a Rails app, and potentially easier to debug. The pros of Rails are that Ruby is expressive and the framework is opinionated so it's easy to understand what other people's code is doing and with Rails you get database migrations. Hope that answered your question.