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.
Dependencies
First we’ll get ActionText and the Cloudinary gem up and running.
> rails action_text:install
> bundle add cloudinary
Configuration
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") %>
and config/environments/production.rb
# config/environments/production.rb
config.active_storage.service = :cloudinary
Javascript
To use direct uploading (uploaded images never touch your server), you will need to include the ActiveStorage JS in app/javascript/packs/application.js
. We’re also going to include the Trix editor JS code and ActionText which supports it:
# app/javascript/packs/application.js
require("@rails/activestorage").start()
require("trix")
require("@rails/actiontext")
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 title
and content
attributes, both of type text
.
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 %>
The 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 %>
Done yet?
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 content
.
Open up a file that the ActionText install generated for us and modify to use Cloudinary and replace image_tag
calls:
# 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.