Getting Started with Cloudinary in Ruby on Rails 6

Photo by Ruan Carlos on Unsplash

Image and file storage is a hurdle that every website or application worth its salt needs to jump. Cloudinary is a robust tool that tackles the task of file uploading and referencing with easy implementation, and a free tier that allows developers to learn the tools before rolling out their functionality into a production deployment. In Rails 6, we can use the existing Active Storage framework to cleanly connect to Cloudinary and allow our users to upload images and files seamlessly to our application. The Cloudinary docs and blogs are quite extensive and informative, however, I still ran in to some speed bumps while setting up the gem with Rails 6. In this article, I’ll address the basics of the setup, and consolidate the best posts that dive deeper into these details.

First things first, let’s set up the gem. For my examples, I’ll be using Ruby 2.7.2, and Rails 6.1.3. For Rails 6, we can install the gem with this command in our terminal:

$ gem install cloudinary

As long as we are running Rails 3.x or higher, we can add the gem to our Gemfile and run bundle install :

gem 'cloudinary'

Check out the Readme of the official gem’s Github repo for more info on installation.

Before the next step, you will need to sign up for a free Cloudinary account here.

Next, we must ensure that our Rails app is using it’s built-in framework, Active Storage, and that it’s connecting to Cloudinary to store files, rather than a folder on our local machine.

Active Record is setup is pretty straight forward, and will make some changes automatically when activated. Let’s start by installing Active Storage by running this line from your root directory:

$ bin/rails active_storage:install

This will generate a new migration file in db/migrate. To run this migration and thus add these new tables to our database, we run:

$ rake db:migrate

Active Storage uses 2 main tables to keep track of the data stored on your objects: active_storage_blobs and active_storage_attachments. The latter uses a polymorphic reference column, meaning it can refer to many kinds of objects from your database. However, if the model that you are using changes name, your migrations will have to be re-run in order for Active Storage to continue working. The Rails Guides are quite comprehensive in edge cases for this setup.

We’ll need to change up a few files to finish connecting to our Cloudinary account.

In Rails 6, we will use a .ymlfile in our config folder, config/cloudinary.yml. Here, we can specify our connection details for each of our environments.

---
development:
cloud_name: bm4l2f1pq
api_key: '384786930741074'
api_secret: "-LladYP3cuLWz1QM3o_wpofEaWI"
enhance_image_tag: true
static_file_support: false
production:
cloud_name: bm4l2f1pq
api_key: '384786930741074'
api_secret: "-LladYP3cuLWz1QM3o_wpofEaWI"
enhance_image_tag: true
static_file_support: true
test:
cloud_name: bm4l2f1pq
api_key: '384786930741074'
api_secret: "-LladYP3cuLWz1QM3o_wpofEaWI"
enhance_image_tag: true
static_file_support: false

On your Cloudinary dashboard, this file can be generated automatically and downloaded with your account details, then dropped right in to your config folder:

Next, we have to tell Active Storage to use Cloudinary to store files, rather than local storage. Let’s first declare cloudinary as a service in our config/storage.yml file:

cloudinary:
service: Cloudinary

This can be expanded later if needed for environment specific customization, but for now, this is enough to get the service up and running.

Finally, we need to update our environment in config/environments/development.rb:

config.active_storage.service = :cloudinary

This setting must match the declaration in the config/storage.yml file. It even has a comment to this effect:

# Store uploaded files on the local file system (see config/storage.yml for options).

We should now be set up to use our Cloudinary account to accept, store, and deliver images and other media files to our application.

Now that we have our application configured, we can use a macro to set up one of our models to use Active Storage. If we’d like to allow users to upload an avatar image for their account, we can set up our model models/user.rb like this:

class User < ApplicationRecord
has_one_attached :avatar
end

In this case, :avatar refers to the identifier of the files we will attach. We can name this whatever we like, but we will use this identifier later on! This is putting Active Storage to work, and there are many other tools in the Active Storage arsenal. Be sure to take a look at the docs.

Now that we have everything installed and configured, we can finally start work on our form to add an avatar image attachment to a user object. Let’s use a form builder in our edit.html.erb file:

<%= form_for @user do |f| %>
<%= f.file_field :avatar %>
<%= f.submit "Update User" %>
<% end %>

This generates a super-simple HTML form for Active storage to use:

In our users_controller.rb file, we simply add the :avatar parameter to our permitted params list:

When submitted, this form automatically uploads the selected file to the root directory of your Cloudinary account, and creates the appropriate Active Storage database entries to bind that file to our user.

active_storage_attachments, the table created by our Active Storage migration now acts as a join table between your user table and the active_storage_blobs table. The referenced blob will store a few important data points:

  • :key- This will later be used by Cloudinary to reference the stored information.
  • :filename- The original name of the file uploaded.
  • :content_type- The type of file being stored.
  • :service_name- Should read ‘cloudinary’ if everything is set up correctly.

When you have an attached file on an object, like our user example, we can check these entries in the database using the identifier we defined earlier in our rails console:

$ User.first.avatar_blob 
#<ActiveStorage::Blob:0x00007f97c92684b0> {
:id => 11,
:key => "1i7pq5wwbg5harkjsv5dhh8j4dpt",
:filename => "profile_pic.jpg",
:content_type => "image/jpg",
:metadata => {
"identified" => true,
"analyzed" => true
},
:service_name => "cloudinary",
:byte_size => 94559,
:checksum => "jqMqh+i6HnjegqnVe7h2Og==",
:created_at => Sun, 07 Mar 2021 18:40:39.646523000 UTC +00:00
}
=> nil

The :key attribute correlates with the file visible in your Cloudinary Media Library.

Cloudinary is a powerful tool, allowing the developer to make transformations to the media before it even gets sent to your view. A common use case would be storing a large photo, but only downloading a small version for a thumbnail or profile photo when needed. This will make your page snappier and cleaner for the end user. As useful as these tools are, there are a couple details that aren’t really explained, and can be tricky the first time around.

Cloudinary uses data included in the request URL to make the proper transformations before sending back the transformed photo. Cloudinary adds a few useful macros and methods that help us build these urls programatically:

<% if @user.avatar.attached? %>
<%= cl_image_tag @user.avatar.key, :width=>150, :crop=>"fill" %>
<% end %>

Will generate something like this HTML:

<img width="150" src="http://res.cloudinary.com/bm4l2f1ot/image/upload/c_fill,w_150/xodacelv7zuttaq3yl6udy0f6l8t.jpg">

There are a few other helpers that we can explore and utilize. For example, we could also generate this URL directly with the Active Record Object .url generator:

<img src=<%= @user.avatar.url(:width=>150, :crop=>"fill") %>>

Notice how our :width, and :crop parameters got converted into c_fill,w_150 in the URL. This tells Cloudinary to apply this cropping and size the image down to a width of 150px before sending it to the browser. It also told the <img> to display the image with a width of 150px all on its own.

***a couple issues that can arise at this stage***

  • Always check your elements in the inspector of your browser and ensure that your transformations are making it into the URL of the img tag! Sometimes there may be a syntax error that will not cause the image to resize until the browser, which means you are still loading the full image!
  • Sometimes leaving off certain attributes will result in the URL not taking on the proper transformation keys. For example, you MUST include a :crop tag when declaring :width or :height.
  • I’ve seen varying instructions on the first argument to be passed in to cl_image_tag, however it seems it should always be the key stored in the Active Storage Blob. The key or anything else in that first argument will just get inserted directly into the url.
<%= cl_image_tag @user.avatar %>

will be incorrectly parsed into:

<img src="http://res.cloudinary.com/bm4l2f1os/image/upload/%23%3CActiveStorage::Attached::One:0x00007fbea774f2f8%3E">

In this case, the actual Active Storage object got inserted into the URL, obviously breaking the code, but using @user.avatar.key in the initial tag fixes this. There are other tags that can be utilized in different situations. Just like most Ruby Gems, you can dig in to the “magic” under the hood and find out what the code is really doing to better write and understand the surface code.

Take the time to explore the breadth of transformations available with Cloudinary using the tools available on their site itself or here in the documentation. There are several helper methods that come along with the Gem to discover, so have fun with it! I still found it necessary to keep close track of all the moving parts to ensure things ran smoothly, but the payoff is huge in the end!

This only begins to scratch the surface of the capability of Active Storage and Cloudinary, but I hope you found some of this information useful!

Software Engineering student at Flatiron School

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store