Uploading Profile Pictures in a React and Rails API App Part II
Tags: rails, react, ruby, javascript
Published: 2020-04-12 22:36:20

### 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](https://dev.to/racheladaw/uploading-profile-pictures-in-a-react-and-rails-api-application-part-i-3nan). 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](https://medium.com/better-programming/how-to-upload-images-to-a-rails-api-and-get-them-back-again-b7b3e1106a13). 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](https://edgeguides.rubyonrails.org/active_storage_overview.html):

"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](https://edgeguides.rubyonrails.org/active_storage_overview.html#has-one-attached), 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](https://edgeguides.rubyonrails.org/active_storage_overview.html) 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](https://medium.com/better-programming/how-to-upload-images-to-a-rails-api-and-get-them-back-again-b7b3e1106a13). 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](https://cloudinary.com/). 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](https://github.com/cloudinary/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](https://medium.com/better-programming/how-to-upload-images-to-a-rails-api-and-get-them-back-again-b7b3e1106a13) 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](https://dev-to-uploads.s3.amazonaws.com/i/6ij1c4mf7zuwigv8zs3o.png)

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](https://dev-to-uploads.s3.amazonaws.com/i/pauq67kdgd7p3t34r9ia.png)

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](https://github.com/racheladaw/concert-buddy-app-backend) is the repo for my Rails API if you would like to look at any of the code further.