CarrierWave integration
Last updated: Apr-18-2024
If you support dynamically uploading images in your Ruby on Rails application, the images are probably attached to a certain model entity. Rails uses ActiveRecord for model entities by 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.
The CarrierWave gem can be useful for integrating image uploads with your model. By default, CarrierWave stores images on the local hard drive, but it also has additional plugins available for image storing and transformation.
The Cloudinary gem provides a plugin for CarrierWave. Using this plugin enables you to 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 in the cloud, and delivered automatically through a CDN.
CarrierWave installation and setup
To use the optional integration module for uploading images with ActiveRecord or Mongoid using CarrierWave, install the CarrierWave gem:
gem install carrierwave
Rails 3.x Gemfile:
gem 'carrierwave'
gem 'cloudinary'
Rails 5.x environment.rb
config.gem 'carrierwave', version: '~> 0.4.10'
config.gem 'cloudinary'
Upload examples
Below is a short example that demonstrates using Cloudinary with CarrierWave in your Rails project. In this example, we use the Post model entity to support attaching 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. For details, see the CarrierWave documentation.
In this example, we'll convert the uploaded image to a PNG before storing it in the cloud. We'll also assign it the post_picture
tag. We define two additional transformations required 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
In the following example, we explicitly define a public_id based on 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 without 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])
At this point, the image uploaded by the user to your server is uploaded to Cloudinary, which also assigned the specified 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; they are generated only when an end-user accesses them 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 and the 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 CarrierWave resize and crop options. In addition, you can apply any custom transformation supported by Cloudinary using the cloudinary_transformation
method. Calling cloudinary_transformation
can be also done in combination with the standard CarrierWave 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 to achieve more complex results. These 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 versions can be a hassle. You can still use CarrierWave and leverage Cloudinary's dynamic transformations by applying desired transformations while building your view. Any version can be generated dynamically from your view with no dependency on CarrierWave versions. To accomplish this, use the full_public_id
attribute with cl_image_tag
to build 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 coordinate-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 to enable 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 returns a Hash of additional Cloudinary 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 an incoming transformation. This way, the original image is not stored in the cloud; only the cropped version. 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
Check out our Introduction to Cloudinary for Ruby Developers course in the Cloudinary Academy. This self-paced resource provides video-based lessons, sample scripts and other learning material to get you going with Ruby and Cloudinary today.