Rails & CarrierWave integration

Overview

Cloudinary is a cloud-based service that provides an end-to-end image management solution including uploads, storage, administration, image manipulation, and delivery.

Cloudinary's Ruby GEM wraps Cloudinary's upload API for easily uploading images and raw files to the cloud. In addition, the Ruby library simplifies the generation of image manipulation URLs and includes view helper methods for embedding images and transformed images in your web views.

If you support dynamically uploading images in your Ruby on Rails application, the images are probably attached to a certain model entity. Rails is using ActiveRecord for model entities in default. While Mongoid documents are used for a MongoDB based model. Examples might include keeping the image as the 'picture' attribute of a Post entity or as the 'profile_picture' attribute of a User entity.

There's a great Ruby GEM for integrating image uploads with your model. It is called CarrierWave. By default, CarrierWave stores images on the local hard drive, but it also has plenty more plugins available for image storing and manipulation.

Cloudinary GEM provides a plugin for CarrierWave. Using it means that you enjoy the benefits of CarrierWave for easily uploading images from HTML forms to your model, while enjoying the great benefits of Cloudinary: uploaded images are stored in cloud, transformed and manipulated in the cloud, and delivered automatically through a CDN.

CarrierWave integration

If you would like to use our optional integration module of image uploads with ActiveRecord or Mongoid using CarrierWave, install CarrierWave GEM:

gem install carrierwave

Rails 3.x Gemfile:

gem 'carrierwave'
gem 'cloudinary'

Rails 2.x environment.rb

config.gem 'carrierwave', :version => '~> 0.4.10'
config.gem 'cloudinary'

Note: The CarrierWave GEM should be loaded before the Cloudinary GEM.

Below we have provided quick instructions for using Cloudinary with CarrierWave in your Rails project.

In our example, we have the Post model entity. You can attach an image to each post. Attached images are managed by the 'picture' attribute (column) of the Post entity.

To get started, first define a CarrierWave uploader class and tell it to use the Cloudinary plugin. (See CarrierWave documentation for more details).

In this example we want to convert the uploaded image to a PNG before storing it in the cloud and we want to assign it the post_picture tag. We also define two required transformations for displaying the image in our site: :standard and :thumbnail. A randomly generated unique Public ID is generated for each uploaded image and persistently stored in the model.

class PictureUploader < CarrierWave::Uploader::Base

  include Cloudinary::CarrierWave

  process :convert => 'png'
  process :tags => ['post_picture']
  
  version :standard do
    process :resize_to_fill => [100, 150, :north]
  end
  
  version :thumbnail do
    resize_to_fit(50, 50)
  end

end

You can specify a custom Public ID. In the following example it is the content of the short_name attribute of the Post entity.

class PictureUploader < CarrierWave::Uploader::Base      
  include Cloudinary::CarrierWave

  ...
        
  def public_id
    return model.short_name
  end  
end

The picture attribute of Post is simply a String (a DB migration script is needed of course). We mount it to the PictureUploader class we've just defined:

class Post < ActiveRecord::Base
  ...
  mount_uploader :picture, PictureUploader
  ...
end

In our HTML form for Post editing, we add a File field for uploading the picture and also a hidden cache field for supporting page reloading and validation errors while not losing the uploaded picture. The example below is in HAML (you can of course do exactly the same using ERB)

= form_for(:post) do |post_form|
  = post_form.hidden_field(:picture_cache)
  = post_form.file_field(:picture)

In our controller, we save or update the attributes of the post in the standard way. For example:

post.update_attributes(params[:post])

Note: if you are using direct image uploads from the browser to Cloudinary, a signed identifier is sent to the controller instead of the actual image data. Cloudinary's CarrierWave plugin seamlessly handles direct identifiers, verifies the signature and updates the model with a reference to the uploaded image.

At this point, the image uploaded by the user to your server is uploaded to Cloudinary, which also assigned the given Public ID and tag and converts it to PNG. The Public ID together with the version of the uploaded image are stored in the 'picture' attribute of our Post entity. Note that by default the transformations are not generated at this point, but instead are only generated when accessed for the first time. This is true unless you specify process :eager => true or simply eager for each transformation.

class PictureUploader < CarrierWave::Uploader::Base
  include Cloudinary::CarrierWave

  version :standard do
    process :eager => true
    process :resize_to_fill => [100, 150, :north]          
  end
  
  version :thumbnail do
    eager
    resize_to_fit(50, 50)
  end
end

Now you can use standard image_tag calls for displaying the uploaded images and their derived transformations. Cloudinary GEM automatically generates the correct full URL for accessing your resources:

image_tag(post.picture_url, :alt => post.short_name)

image_tag(post.picture_url(:thumbnail), :width => 50, :height => 50)

Custom and dynamic transformations

Cloudinary's plugin for CarrierWave supports all standard resize and crop of CarrierWave. In addition, you can apply any custom transformation supported by Cloudinary.

You can use the cloudinary_transformation method for specifying any supported transformation parameters. Calling cloudinary_transformation can be also done in combination with the standard resize and crop methods. The following uploader class shows a common example of using custom transformations:

class PictureUploader < CarrierWave::Uploader::Base      
  include Cloudinary::CarrierWave
  
  # Generate a 164x164 JPG of 80% quality 
  version :simple do
    process :resize_to_fill => [164, 164, :fill]
    process :convert => 'jpg'
    cloudinary_transformation :quality => 80
  end
  
  # Generate a 100x150 face-detection based thumbnail, 
  # round corners with a 20-pixel radius and increase brightness by 30%.
  version :bright_face do
    cloudinary_transformation :effect => "brightness:30", :radius => 20,
      :width => 100, :height => 150, :crop => :thumb, :gravity => :face
  end

end

You can take this further and apply chained transformations for reaching complex results. Any transformations can be applied as an incoming transformation while uploading or as part of the different versions that are generated either lazily or eagerly while uploading. The following uploader class includes such chained transformations applied using the transformation parameter of the cloudinary_transformation method.

class PictureUploader < CarrierWave::Uploader::Base      
  include Cloudinary::CarrierWave
 
  # Apply an incoming chained transformation: limit image to 1000x1200 and 
  # add a 30-pixel watermark 5 pixels from the south east corner.   
  cloudinary_transformation :transformation => [
      {:width => 1000, :height => 1200, :crop => :limit}, 
      {:overlay => "my_watermark", :width => 30, :gravity => :south_east, 
       :x => 5, :y => 5}
    ]        
  
  # Eagerly transform image to 150x200 with a sepia effect applied and then
  # rotate the resulting image by 10 degrees. 
  version :rotated do
    eager
    cloudinary_transformation :transformation => [
        {:width => 150, :height => 200, :crop => :fill, :effect => "sepia"}, 
        {:angle => 10}
      ]
  end
end

Some websites have a graphic design that forces them to display the same images in many different dimensions. Formally defining multiple uploader's versions might become annoying.

You can still use CarrierWave and leverage Cloudinary's dynamic transformations by applying any desired transformation while building your view. Any version can be generated dynamically from your view without depending on CarrierWave versions. Simply use the full_public_id attribute with cl_image_tag for building cloud-based transformation URLs for the uploaded images attached to your model.

cl_image_tag(post.picture.full_public_id, :format => "jpg", :width => 100, :height => 200, 
             :crop => :crop, :x => 20, :y => 30, :radius => 10)

Custom coordinates based cropping

If you allow your users to manually select the cropping area, we recommend to keep the x,y coordinates persistently in the model for being able to perform different cropping on the original image in the future. The following uploader class fetches the custom coordinates from attributes of the model object. The custom_crop method in this example simply returns a Hash of additional Cloudinary's transformation parameters to apply.

class PictureUploader < CarrierWave::Uploader::Base  
  include Cloudinary::CarrierWave  

  version :full do    
    process :convert => 'jpg'
    process :custom_crop
  end    
  
  def custom_crop      
    return :x => model.crop_x, :y => model.crop_y, 
      :width => model.crop_width, :height => model.crop_height, :crop => :crop      
  end
end

If you want to store only the cropped version of the image, you can use the incoming transformation of our upload API. This way the original image is not stored in the cloud. Only the cropped version is stored. You can then use further transformations to resize the cropped image. The following example calls process :custom_crop in the class itself (instead of in a 'version') while the custom-coordinates are kept as transient attributes on the model (defined with attr) instead of storing them persistently.

class PictureUploader < CarrierWave::Uploader::Base  
  include Cloudinary::CarrierWave  

  process :custom_crop

  version :thumbnail do    
    process :convert => 'jpg'
    resize_to_fill(120, 150, 'North')
  end    
  
  def custom_crop      
    return :x => model.crop_x, :y => model.crop_y, 
      :width => model.crop_width, :height => model.crop_height, :crop => :crop      
  end
end