Hackernoon logoImage Storage in Rails Apps Using Cloudinary and Active Storage by@bruna

Image Storage in Rails Apps Using Cloudinary and Active Storage

Bruna Hacker Noon profile picture


Ruby on Rails has an awesome gem to upload, store and display images: Active Storage. It's very easy to use and it comes with a local disk-based service, that should be enough to store everything you need if you just want to practice using Rails. 

However, when you start building more elaborated websites and deploy them to production, you'll start noticing the limitations of Active Storage's local disk. 

In Heroku, for example, although you are able to upload images using disk storage and everything will look fine, these files will disappear after the application is automatically restarted (which happens once every 24 hours on the free plan). 

Of course, this all makes sense. The purpose of the local disk is for development and testing.

For production, Active Storage facilitates uploading files to cloud storage services, such as Amazon S3, Google Cloud Storage, an Microsoft Azure Storage, which are suggested by Rails Guides.

Or, if you are a developer student like me and want to save any possible dollar, you can use Cloudinary.

Why choose Cloudinary?

The first reason that made me choose Cloudinary was the price. It's free! At least for my needs and, I think, for most of yours as well. The Free Classic Plan gives you 10GB of storage, and it's not just for one year, it's forever.

The second reason was the possibility to use it on my existing Rails application. Cloudinary provides a Ruby Gem that simplifies the integration with Ruby on Rails applications, which, according to the documentation, presents the following features:

  • Methods to build URLs for image and video manipulations;
  • Rails view helper tags for embedding and transforming images;
  • API wrappers for file upload, administration, sprite generation and more;
  • Server-side file upload plus direct unsigned file upload from the browser using the jQuery plugin;
  • Migration tool;
  • Active Record integration;
  • CarrierWave plugin;
  • Static image syncing for CDN delivery;
  • General Ruby integration for non-Rails frameworks such as Sinatra.

Not enough, Cloudinary also allows you to manipulate uploaded images simply by passing a set of transformation parameters on the dynamic URL in your views. For example, you can resize and crop the images, change its format and quality, rotate, and apply a large variety of special effects and filters. All manipulations are performed automatically in the cloud. The free plan offers 20,000 monthly transformations.

Setting up Cloudinary on your Rails application

The first step, of course, it's to sign up for a Cloudinary free account.

Then, to install the Cloudinary Ruby gem on your project, add the following line to the Gemfile:

gem 'cloudinary'

And run bundle install.

To use the library, you need to configure your project to provide Cloudinary's cloud_name, api_key, and api_secret. These credentials can be found on the Dashboard page of your account.

Setting the configuration parameters can be done globally by creating a cloudinary.yml file under the config directory of your Rails project. The contents of this file should look like this:

  cloud_name: "sample"
  api_key: "874837483274837"
  api_secret: "a676b67565c6767a6767d6767f676fe1"
  secure: true
  cdn_subdomain: true

You can download your customized cloudinary.yml configuration file through Cloudinary's Management Console, and just place it under the config directory. You might also define several optional configuration parameters if relevant for your project.

So, now that everything is configured for using the gem, you will need to create the functionality for uploading and displaying the images on your website.

Cloudinary offers many uploading options, such as an upload widget, server-side upload, and direct uploading from the browser. Moreover, it also supports integration with other libraries, like CarrierWage, Attachinary, and Active Storage, which can be useful for integrating image uploads with your models.

In my case, my application was almost ready when I realized I needed an alternative to store the images. Until there, I was using Active Storage's local disk, and since I didn't want to change so much of my code, my decision was pretty clear.

Integrating Cloudinary with Active Storage

The Cloudinary gem allows you to use all its great features, while still using Active Storage to easily upload images from HTML forms to your model.

Assuming you have already configured your application to use Active Storage (if not, I recommend reading this article), the following steps are pretty straightforward.

1. Declare the Cloudinary service in the config/storage.yml file by adding a new entry with a custom name (e.g., cloudinary) and the service configuration:

  service: Cloudinary

2. In each environment you want to use Cloudinary, you have to tell Active Storage to use this service by updating config.active_storage.service. For example, if you want to use it on the production environment, you need to have the following line on the config/environments/production.rb:

# Store uploaded files on the local file system (see config/storage.yml for options).
  config.active_storage.service = :cloudinary

3. After setting up Cloudinary as the service, you will use direct uploading, which is already supported by Active Storage. To do that, include activestorage.js in your application's JavaScript bundle:

//= require activestorage

4. On the form, include the direct upload URL on the input field:

<%= form_for @post do |f| %>

        <%= f.file_field :picture, class: 'form-control', direct_upload: true  %>

        <%= f.submit 'Create', class: 'btn sign-up-button btn-outline-secondary w-100 mt-3' %>

<% end %>

5. Now you just need to display the image in the browser. Use the url_for helper method to generate a permanent URL link, that upon access, redirects to a Cloudinary endpoint.

<%= url_for(@post.picture) %>

One problem and how I solve it:

Although with these configurations uploading and storage work fine, in the browser vertical images were being displayed in the wrong orientation. 

Turns out this happens because pictures captured on cameras, including from mobile devices, are stored with a series of metadata. One of them is called EXIF (exchangeable image file format), which informs the image's orientation. It seems that not all browsers have the support to get this information and apply the rotation automatically. 

Searching about this problem, I found out that if the image is transformed, Cloudinary will automatically orientate the image based on its EXIF information, and the image will be delivered with the correct orientation. 

Easy enough, no? As I said before, it's very easy to manipulate uploaded images on Cloudinary, simply by passing a set of transformation parameters on the dynamic URL in your views. 

Well, not so much. Even though the documentation states that the url_for helper method was enhanced to accept any Cloudinary image transformation as a parameter, when I tried to do that, it results in an error saying this method only accepts 0..1 argument. 

I couldn't find any answers to this problem yet, but searching through Cloudinary's settings, I found out that is possible to set up default transformations without changing my code.

In your account settings, under the Upload tab, you will find a section for upload presets.

Click on add presets, and in the Upload Manipulations tab, you will find Incoming Transformations. When you click on edit, you will see all the available transformation options, including automatic rotation and use EXIF data.

In my case, I select the following transformations: use EXIF data, a default width, and quality automatic - good.

And ta-dah!! Everything looks perfect now and my website it's fully functional, no more vanishing images (and for free!).

Update (april 8th 2020)

I contacted Cloudinary's support team about this error I was getting, and it turns out it was a mistake in the documentation. So, instead of using the url_for helper method, you need to use one of these:

<%= cl_image_tag(@user.avatar.key, width: 200, crop: :scale) %>

// OR

<%= cloudinary_url(@user.avatar.key, options = {}) %>


Join Hacker Noon

Create your free account to unlock your custom reading experience.