Skip to content Skip to sidebar Skip to footer

Rails Simple Form Upload Collection of Images

With a Rails API, image uploading isn't equally simple as it seems

Photo by Alexander Andrews on Unsplash

I'd given myself a calendar week to write a Rail API dorsum cease for Supagram, a lightweight, browser-based Instagram clone featuring posts, likes, and a chronological activity feed from the users you follow.

The biggest difficulty I foresaw was the polymorphic database relationships betwixt users in their diverse roles as follower, followed, liker, so on. Little did I know that the least intuitive matter to become working would really be the elementary concept effectually which all of Instagram is built: epitome uploading.

Let me walk you through the problem. This server was required to:

  • Take an image file from the React forepart end
  • Associate the image with a newly created postal service record in the database
  • Upload the image to a content delivery network similar Cloudinary or AWS for storage and retrieval
  • Fetch the image URL and return it as confirmation of the mail service'south cosmos and in subsequent GET requests to the activity feed

The existing resources on how to do this are extremely fragmented. Much had to be inferred or jury-rigged for the specific context of a Rails API. Here's the definitive, pace-past-pace guide to save you lot the pain.

1. Accept an Epitome From a JavaScript Forepart

In that location are ii common ways to transport image uploads to a server: as FormData, or as a base64 string. In that location are considerable functioning disadvantages to base64 encoding and transmission, so I chose FormData.

I'll be using React components to demonstrate, but FormData is a spider web API — it's not constrained to whatever particular framework, or even JavaScript itself.

Hither a uncomplicated HTML grade takes a caption and an epitome to upload. The names of the fields should match the parameters your API endpoint is expecting to receive, in this case caption and epitome.

On submit, we preclude the default course behaviour (which refreshes the page) and employ JavaScript'southward FormData constructor to create a FormData object from consequence.target — the whole form.

That washed, nosotros make our outset call to the API:

There are ii important things to annotation about the config object for this request:

  • In that location is no "Content-Type" fundamental in the headers — the content type is multipart/class-information, which is implied by the FormData object itself.
  • The body is non stringified. The FormData API handles all the necessary processing for the image to be sent over the web.

The dominance header is optional and will depend on the requirements of the endpoint you're posting to. In the example, the endpoint I'm using is represented by POSTS_URL.

2. Associate the Image With a Newly-Created Post Tape in the Database

On the back end, I used ActiveStorage to create associations between images and their owning objects. It'due south the standard gem for file associations as of Rails 5.2 and is steadily replacing older solutions similar CarrierWave and Paperclip.

To get started, only run rail active_storage:install. Information technology will create migrations for two new tables in your database, active_storage_blobs and active_storage_attachments. These are managed automatically; yous don't need to touch them. Run track db:drift to complete the process.

Past default, ActiveStorage will use local storage for uploaded files while running in a development environs. In production, this is almost certainly non what you want. Information technology likewise poses some unique challenges for returning image URLs from the server. We'll configure this properly in office 3, after we take a await at our Post model and accompanying endpoint.

Post migration/model

Report this migration for my post model. There's something odd nigh it.

Y'all'll notice that in that location is nothing about an image hither. Neither the image nor a reference to the image lives in the Posts tabular array.

Now check out the model:

The essential line here is has_one_attached :image. This tells ActiveStorage to associate a file with a given instance of Mail.

The name for the fastened object should lucifer the parameter being sent from the front end cease. I've called it :image, because that's what I named the corresponding upload form field. You tin call it whatever you like, so long every bit the front cease and dorsum end concur.

As a bonus, I've added validation to ensure that posts cannot exist created without images. Alter this to conform your purposes.

Curious well-nigh the include statement and my get_image_url method? Allow'south inspect the post cosmos endpoint before coming back to these.

Post creation endpoint

The post_params method is arguably the most important here. The data from our front end end has ended up in a Rails params hash with a trunk that looks roughly like: { "caption" => "Great caption", "image" => <FormData> }.

The keys of this hash must match the attributes expected by the model.

My particular post model requires a user_id, which wasn't sent in the request torso but is instead decoded from an Authorization token in the asking headers. That's happening behind the scenes in get_current_user(), and y'all don't need to worry about information technology.

When yous laissez passer post_params() to Mail.create(), ActiveStorage kicks in, saves a file based on the FormData contained within the image param, and assembly the file with the new Post tape. If you're using local storage, images will be saved in root/storage by default. Yet, that's probably not what you desire.

three. Upload the Paradigm to a CDN for Storage and Retrieval

Local storage eats up infinite and can't compete with the delivery speeds of defended content delivery networks like Cloudinary and AWS. Whatsoever your purposes, it's a good idea to familiarise yourself with these essential services.

Cloudinary is exceptionally user-friendly and easy to integrate with ActiveStorage, then that's the approach I took for this projection. From this point on, I'll assume that yous already have a (complimentary) Cloudinary business relationship. If y'all'd adopt to use some other service, don't worry — the approach is largely the same for all major providers.

First, add the cloudinary gem to your Gemfile and run bundle install.

Then, in /config, open ActiveRecord's storage.yml configuration file and add the following. Don't alter annihilation else.

Next, navigate to config/environments/development.rb and ./production.rb, and set config.active_storage.service to :cloudinary in each. Your test environment will continue to apply local storage by default.

Finally, download the cloudinary.yml config file from your Cloudinary dashboard and place it in the /config folder.

Find the YML download link in the height-right of your dashboard Account Details section.

Caution: This file contains the hush-hush key for your Cloudinary account. Do not share this file or push button it to your git repo, or your account can be compromised. Include /config/cloudinary.yml in your .gitignore file. If you do reveal these details by accident (I'yard speaking from feel), immediately deactivate the compromised key and generate a new one via your Cloudinary dashboard. Update cloudinary.yml to reflect the new secret central.

With this in place, ActiveStorage volition automatically upload and retrieve images from the cloud.

4. Fetch the Image URL and Return It

This is the least intuitive part of working with ActiveStorage. Getting images in is like shooting fish in a barrel enough. Getting them out again without guidance is like solving a 12-sided Rubik'south cube boozer.

Information technology becomes specially convoluted if, like me, you want to move the logic of edifice your endpoint's response into a dedicated serializer class.

In my posts controller respond_to_post() method, I first bank check if the new mail is valid and, if so, create an instance PostSerializer from the new post and the current user, and render JSON with the serializer's serialize_new_post() method.

In PostSerializer, I pull together details about the post, including the URL that will redirect our end user to the image hosted by Cloudinary. If it seems odd to explicitly pass the instance variable @post to the instance method serialize_post, ignore it — it'south a requirement of other PostSerializer functions that aren't relevant to this post. If you're curious, the full source code is here. Similarly, the contents of the serialize_user_details method are unimportant.

But how, exactly, does mail.get_image_url() work, and where does information technology come from?

This is a method I defined on the Post model itself, the image URL being a pseudo-attribute of the postal service. It made sense to me that the postal service should know about its image URL.

To access the URL that ActiveStorage creates for each image, we use Rails' url_for() method. But there's a snag: Models don't normally have access to Rails' url_helpers. It'south necessary to include Rails.application.routes.url_helpers at the summit of the course before you can use it.

If yous attempt to hit your endpoint from the front end at this stage, you'll probable meet this error on the back end:

            ArgumentError (Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to truthful)          

To resolve it, navigate to config/environments/development.rb and add together Rails.application.routes.default_url_options = { host: "http://localhost:3000" } (or your preferred evolution port, if not 3000). In ./production.rb, do the same, using the web root of your production server as the host value.

If it's all working correctly, your endpoint will now render beautifully formatted JSON that includes an epitome link. When clicked or loaded, it will redirect to your Cloudinary-hosted image.

Commit your piece of work, button it to Github, and breathe a sigh of relief.

gonzalezbeciond66.blogspot.com

Source: https://betterprogramming.pub/how-to-upload-images-to-a-rails-api-and-get-them-back-again-b7b3e1106a13

Post a Comment for "Rails Simple Form Upload Collection of Images"