Adding Rich Text Content to Your Rails App With ActionText

Dave Guymon
The Startup
Published in
5 min readSep 13, 2020

One of my favourite projects to work on as a new Ruby on Rails developer is creating a personal blog. There are several quality tutorials online, and working through this project teaches a lot of interesting core concepts, including authentication, authorization, migrations, associations, and more. Once you’ve created a blogging application though, a natural next step is adding the ability to format content with rich text, such as font-size, bold, italic, indentation, and more. The purpose of this article is to walk you through taking the standard Rails textarea form input and enhance it with ActionText’s rich text format functionality.

Photo by Damian Zaleski on Unsplash

General Setup

To start, let’s create a new Rails app, which I will call actiontext_demo.

rails new actiontext_demo

Once that app has been generated, remember to cd into the new app directory.

cd actiontext_demo

Let’s generate a scaffolded Post resource, which we will use with our Rich Text editor. This command will do the heavy lifting of creating our model, migration, controller, and views for our blog’s Post resource (Note: that rather than including a content field in our scaffold command, we will be leveraging ActionText’s virtual content attribute, which we will set up later).

rails generate scaffold Post title:string

At this point, let’s set up our database and migrate our Post table.

rails db:migrate

As a last general setup step, let’s now add a root route for our Post’s index view to the top of our routes.rb file.

Rails.application.routes.draw do
root to: "posts#index"
resources :posts
end

ActionText Installation

Our first ActionText specific step is installing the yarn package, which among other things installs the required JavaScript dependencies and generates migrations for ActiveStorage and ActionText (To keep this particular post short, this tutorial will utilize the ActionText table and leave using the ActiveStorage functionality to enable attachment storage and image embedding for a future post).

bin/rails action_text:install

Upon successful installation, you should see the following changes to application.js:

app/javascript/packs/application.jsrequire("trix")
require("@rails/actiontext")

You will additionally need to require the new ActionText stylesheet in your application.scss file like so:

app/assets/stylesheets/application.scss/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.*
*= require_tree .
*= require_self
*/
@import "./actiontext.scss";

Before moving on, let’s migrate our database again to add the ActiveStorage and ActionText tables to our DB schema.

rails db:migrate
Current view of our Post form without ActionText’s rich text editor.
View of app/views/posts/_form.html.erb without ActionText’s rich text editor.

ActionText Setup

Earlier, when we created our original Post migration via our app’s generated scaffold, we left out the content field in favor of using a virtual attribute made available to us by ActionText. We can set that up by adding the following line to our Post model.

app/models/post.rbclass Post < ApplicationRecord
has_rich_text :content
end

Next, we will add a rich_text_area input form group to our new Post form.

<div class="field">
<%= form.label :content %>
<%= form.rich_text_area :content %>
</div>

The full file should now look like this:

app/views/posts/_form.html.erb<%= form_with(model: post, local: true) do |form| %>
<% if post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% post.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :title %>
<%= form.text_field :title %>
</div>
<div class="field">
<%= form.label :content %>
<%= form.rich_text_area :content %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>

Now, we can render the sanitized rich text in our show and index views.

app/views/posts/show.html.erb<p id="notice"><%= notice %></p><p>
<strong>Title:</strong>
<%= @post.title %>
</p>
<p>
<strong>Content:</strong>
<%= @post.content %>
</p>
<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>
...app/views/posts/index.html.erb<p id="notice"><%= notice %></p><h1>Posts</h1><table>
<thead>
<tr>
<th>Title</th>
<th>Content</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @posts.each do |post| %>
<tr>
<td><%= post.title %></td>
<td><%= post.content %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br><%= link_to 'New Post', new_post_path %>
View of app/views/posts/_form.html.erb with ActionText’s rich text editor.

Though we can now see the awesome Rich Text Formatter that ActionText gives us, if you attempt to create a post right now, the action will fail. In order to fix this, we need to permit our virtual :content attribute in the Posts controller like so:

app/controllers/posts_controller.rb def post_params
params.require(:post).permit(:title, :content)
end

Our posts_controller.rb file should now look like this:

app/controllers/posts_controller.rbclass PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
# GET /posts
# GET /posts.json
def index
@posts = Post.all
end
# GET /posts/1
# GET /posts/1.json
def show
end
# GET /posts/new
def new
@post = Post.new
end
# GET /posts/1/edit
def edit
end
# POST /posts
# POST /posts.json
def create
@post = Post.new(post_params)
respond_to do |format|
if @post.save
format.html { redirect_to @post, notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: @post }
else
format.html { render :new }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /posts/1
# PATCH/PUT /posts/1.json
def update
respond_to do |format|
if @post.update(post_params)
format.html { redirect_to @post, notice: 'Post was successfully updated.' }
format.json { render :show, status: :ok, location: @post }
else
format.html { render :edit }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
@post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Only allow a list of trusted parameters through.
def post_params
params.require(:post).permit(:title, :content)
end
end
Example formatting with ActionText’s rich text editor.

As the last step, to improve performance, let’s update the Post controller’s index action to use a helper method to prevent N+1 database queries.

def index
@posts = Post.all.with_rich_text_content
end

Conclusion

With ActionText, our basic personal blogging app just took a major step toward becoming an engaging and aesthetic online journal for both developer and reader. How are you going to use it in your Ruby on Rails projects?

--

--