I’ve been playing with Cloudinary for image hosting in a web app (direct upload to them). They have a great set of features, whereby a transformation can be specified as part of the image URL.
Pairing this with Trix/ActionText in Rails 6 was simple enough, but there’s a catch later on for embedded images.
First we’ll get ActionText and the Cloudinary gem up and running.
> rails action_text:install > bundle add cloudinary
Then add you cloudinary credentials to the environment. You can obtain the values from the cloudinary.yml and add them to your production (or development) encrypted credentials file:
> rails credentials:edit --environment=production
Here are the keys you need, with values extracted from the cloudinary.yml above:
cloudinary: cloud_name: "example_app" api_key: "444444444444444" api_secret: "AAAAAAAA-BBBBBBBBBBBBBBBBBB" secure: true
With those values safely stored, we need to load them. In a new initializer:
# config/initializers/cloudinary.rb Cloudinary.config do |config| config.cloud_name = Rails.application.credentials.cloudinary[:cloud_name] config.api_key = Rails.application.credentials.cloudinary[:api_key] config.api_secret = Rails.application.credentials.cloudinary[:api_secret] config.secure = true config.cdn_subdomain = true end
Now specify that ActiveStorage should use Cloudinary, editing
config/storage.yml. You can also specify other services and local disk:
# config/storage.yml cloudinary: service: Cloudinary local: service: Disk root: <%= Rails.root.join("storage") %>
# config/environments/production.rb config.active_storage.service = :cloudinary
To use direct uploading (uploaded images never touch your server), you will need to include the ActiveStorage JS in
Ruby & Erb
So now we’re ready for the editor and display of edited content.
In our edit .erb template, we will use the trix editor to manage html content. Let’s assume the age-old
Post model has
content attributes, both of type
Add the following to your model:
# app/models/post.rb has_rich_text :content
Note, if you have been upgrading an older Rails project, ensure you have the following in the
head section of your application layout
<%= csrf_meta_tags %>. This ensures ActionText can send requests to your app without authentication token errors.
# new.html.erb <%= form_for Post.new do |f| %> <%= f.text :title %> <%= f.rich_text_area :content %> <%= f.submit %> <% end %>
rich_area_text input type gives us the wonderful Trix editor, with drag and drop image support.
To display the
Post, we have the following:
# show.html.erb <h1><%= @post.title %></h1> <%= @post.content %>
Not quite. Remember I mentioned a catch? We need to ensure that the Cloudinary image is displayed properly. At the moment, a local URL is likely specified for any images embedded in
Open up a file that the ActionText install generated for us and modify to use Cloudinary and replace
# app/views/active_storage/blobs/_blob.html.erb --- <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> +++ <% if local_assigns[:in_gallery] %> +++ <%= cl_image_tag blob.key, width: 800, height: 600, c_limit: true %> +++ <% else %> +++ <%= cl_image_tag blob.key, width: 1024, height: 768, c_limit: true %> +++ <% end %>
Note that I chose to remove the ternary operator conditional and replace it with two separate
cl_image_tag calls. This allows clearer differences between
in_gallery and other views. The additional parameters to
cl_image_tag are passed to the cloudinary API as transforms. FYI,
c_limit means that an image will not be resized above it’s original size, so the width and height are effectively max values.
How about now?
Yep, we’re done. Assuming you have no CSRF token issues, you should be good to go. Try dragging an image into the editor. It will be immediately uploaded to cloudinary and referenced when your post is submitted.
One thing I’d still like to resolve is the occasional error, which seems to be timing related; submitting the form before the image is uploaded and acknowledged by Cloudinary results in a missing image in the rendered content. We can probably disable the form submit button/action while the upload in in progress, but that will require implementing event hooks. I’m not sure if ActionText can handle that or not yet.