guides/source/action_text_overview.md

Summary

Maintainability
Test Coverage
**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**

Action Text Overview
====================

This guide provides you with all you need to get started in handling rich text
content.

After reading this guide, you will know:

* What Action Text is, and how to install and configure it.
* How to create, render, style, and customize rich text content.
* How to handle attachments.

--------------------------------------------------------------------------------

What is Action Text?
--------------------

Action Text facilitates the handling and display of rich text content. Rich text
content is text that includes formatting elements such as bold, italics, colors,
and hyperlinks, providing a visually enhanced and structured presentation beyond
plain text. It allows us to create rich text content, store it in a table, then
attach it to any of our models.

Action Text includes a [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG) editor
called Trix, which is used in web applications to provide users with a
user-friendly interface for creating and editing rich text content. It handles
everything from providing enriching capabilities like the formatting of text,
adding links or quotes, embedding images, and much much more. You can view an
example of the Trix editor [here](https://trix-editor.org/).

The rich text content generated by the Trix editor is saved in its own RichText
model that can be associated with any existing Active Record model in the
application. In addition, any embedded images (or other attachments) can be
automatically stored using Active Storage (which is added as a dependency) and
associated with that RichText model. When it's time to render content, Action
Text processes the content by sanitizing it first so that it's safe to embed
directly into the page's HTML.

INFO: Most WYSIWYG editors are wrappers around HTML’s `contenteditable` and
`execCommand` APIs. These APIs were designed by Microsoft to support live
editing of web pages in Internet Explorer 5.5. They were eventually
reverse-engineered and copied by other browsers. Consequently, these APIs were
never fully specified or documented, and because WYSIWYG HTML editors are
enormous in scope, each browser's implementation has its own set of bugs and
quirks. Hence, JavaScript developers are often left to resolve the
inconsistencies.<br><br> Trix sidesteps these inconsistencies by treating
`contenteditable` as an I/O device: when input makes its way to the editor, Trix
converts that input into an editing operation on its internal document model,
then re-renders that document back into the editor. This gives Trix complete
control over what happens after every keystroke and avoids the need to use
`execCommand` and the inconsistencies that come along with it.

## Installation

To install Action Text and start working with rich text content, run:

```bash
$ bin/rails action_text:install
```

It will do the following:

- Installs the JavaScript packages for `trix` and `@rails/actiontext` and adds
  them to the `application.js`.
- Adds the `image_processing` gem for analysis and transformations of the
  embedded images and other attachments with Active Storage. Please refer to the
  [Active Storage Overview](active_storage_overview.html) guide for more
  information about it.
- Adds migrations to create the following tables that store rich text content
  and attachments: `action_text_rich_texts`, `active_storage_blobs`,
  `active_storage_attachments`, `active_storage_variant_records`.
- Creates `actiontext.css` and imports it into `application.css`. The Trix
  stylesheet is also included in the `application.css` file.
- Adds the default view partials `_content.html` and `_blob.html` to render
  Action Text content and Active Storage attachment (aka blob) respectively.

Thereafter, executing the migrations will add the new `action_text_*` and
`active_storage_*` tables to your app:

```bash
$ bin/rails db:migrate
```

When the Action Text installation creates the `action_text_rich_texts` table, it
uses a polymorphic relationship so that multiple models can add rich text
attributes. This is done through the `record_type` and `record_id` columns,
which store the ClassName of the model, and ID of the record, respectively.

INFO: With polymorphic associations, a model can belong to more than one other
model, on a single association. Read more about it in the [Active Record
Associations
guide](https://guides.rubyonrails.org/association_basics.html#polymorphic-associations).

Hence, if your models containing Action Text content use UUID values as
identifiers, then all models that use Action Text attributes will need to use
UUID values for their unique identifiers. The generated migration for Action
Text will also need to be updated to specify `type: :uuid` for the record
references line.

```ruby
t.references :record, null: false, polymorphic: true, index: false, type: :uuid
 ```

## Creating Rich Text Content

This section explores some of the configurations you'll need to follow to create
rich text.

The RichText record holds the content produced by the Trix editor in a
serialized `body` attribute. It also holds all the references to the embedded
files, which are stored using Active Storage. This record is then associated
with the Active Record model which desires to have rich text content. The
association is made by placing the `has_rich_text` class method in the model
that you’d like to add rich text to.

```ruby
# app/models/article.rb
class Article < ApplicationRecord
  has_rich_text :content
end
```

NOTE: There's no need to add the `content` column to your Article table.
`has_rich_text` associates the content with the `action_text_rich_texts` table
that has been created, and links it back to your model. You also may choose to
name the attribute to be something different from `content`.

Once you have added the `has_rich_text` class method to the model, you can then
update your views to make use of the rich text editor (Trix) for that field. To
do so, use a
[`rich_text_area`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-rich_text_area)
for the form field.

```html+erb
<%# app/views/articles/_form.html.erb %>
<%= form_with model: article do |form| %>
  <div class="field">
    <%= form.label :content %>
    <%= form.rich_text_area :content %>
  </div>
<% end %>
```

This will display a Trix editor that provides the functionality to create and
update your rich text accordingly. Later we'll go into details about [how to
update the styles for the
editor](action_text_overview.html#removing-or-adding-trix-styles).

Finally, to ensure that you can accept updates from the editor, you will need to
permit the referenced attribute as a parameter in the relevant controller:

```ruby
class ArticlesController < ApplicationController
  def create
    article = Article.create! params.require(:article).permit(:title, :content)
    redirect_to article
  end
end
```

If the need arises to rename classes that utilize `has_rich_text`, you will also
need to update the polymorphic type column `record_type` in the
`action_text_rich_texts` table for the respective rows.

Since Action Text depends on polymorphic associations, which, in turn, involve
storing class names in the database, it's crucial to keep the data in sync with
the class names used in your Ruby code. This synchronization is essential to
maintain consistency between the stored data and the class references in your
codebase.

## Rendering Rich Text Content

Instances of `ActionText::RichText` can be directly embedded into a page because
they have already sanitized their content for a safe render. You can display the
content as follows:

```erb
<%= @article.content %>
```

`ActionText::RichText#to_s` safely transforms RichText into an HTML String. On
the other hand `ActionText::RichText#to_plain_text` returns a string that is not
HTML safe and should not be rendered in browsers. You can learn more about
Action Text's sanitization process in the
[documentation](https://api.rubyonrails.org/classes/ActionText/RichText.html)
for the `ActionText::RichText` class.

NOTE: If there's an attached resource within `content` field, it might not show
properly unless you have the necessary [dependencies for Active
Storage](active_storage_overview.html#requirements) installed.

## Customizing the Rich Text Content Editor (Trix)

There may be times when you want to update the presentation of the editor to
meet your stylistic requirements, this section guides on how to do that.

### Removing or Adding Trix Styles

By default, Action Text will render rich text content inside an element with the
`.trix-content` class. This is set in
`app/views/layouts/action_text/contents/_content.html.erb`.  Elements with this
class are then styled by the trix stylesheet.

If you’d like to update any of the trix styles, you can add your custom styles
in `app/assets/stylesheets/actiontext.css`.

However, if you’d prefer to provide your own styles or utilize a third-party
library instead of the default trix stylesheet, you can remove trix from the
pre-processor directives in the `app/assets/stylesheets/actiontext.css` file by
deleting the following:

```css
= require trix
```

### Customizing the Editor Container

To customize the HTML container element that's rendered around rich text
content, edit the `app/views/layouts/action_text/contents/_content.html.erb`
layout file created by the installer:

```html+erb
<%# app/views/layouts/action_text/contents/_content.html.erb %>
<div class="trix-content">
  <%= yield %>
</div>
```

### Customizing HTML for Embedded Images and Attachments

To customize the HTML rendered for embedded images and other attachments (known
as blobs), edit the `app/views/active_storage/blobs/_blob.html.erb` template
created by the installer:


```html+erb
<%# app/views/active_storage/blobs/_blob.html.erb %>
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
  <% if blob.representable? %>
    <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
  <% end %>

  <figcaption class="attachment__caption">
    <% if caption = blob.try(:caption) %>
      <%= caption %>
    <% else %>
      <span class="attachment__name"><%= blob.filename %></span>
      <span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
    <% end %>
  </figcaption>
</figure>
```

## Attachments

Currently, Action Text supports attachments that are uploaded through Active
Storage as well as attachments that are linked to a Signed GlobalID.

### Active Storage

When uploading an image within your rich text editor, it uses Action Text which
in turn uses Active Storage. However, Active Storage has [some
dependencies](active_storage_overview.html#requirements) which are not provided
by Rails. To use the built-in previewers, you must install these libraries.

Some, but not all of these libraries are required and they are dependent on the
kind of uploads you are expecting within the editor. A common error that users
encounter when working with Action Text and Active Storage is that images do not
render correctly in the editor. This is usually due to the `libvips` dependency
not being installed.


### Signed GlobalID

In addition to attachments uploaded through Active Storage, Action Text can also
embed anything that can be resolved by a [Signed
GlobalID](https://github.com/rails/globalid#signed-global-ids).

A Global ID is an app-wide URI that uniquely identifies a model instance:
`gid://YourApp/Some::Model/id`. This is helpful when you need a single
identifier to reference different classes of objects.

When using this method, Action Text requires attachments to have a signed global
ID (sgid). By default, all Active Record models in a Rails app mix in the
`GlobalID::Identification` concern, so they can be resolved by a signed global
ID and are therefore `ActionText::Attachable` compatible.

Action Text references the HTML you insert on save so that it can re-render with
up-to-date content later on. This makes it so that you can reference models and
always display the current content when those records change.

Action Text will load up the model from the global ID and then render it with
the default partial path when you render the content.

An Action Text Attachment can look like this:

```html
<action-text-attachment sgid="BAh7CEkiCG…"></action-text-attachment>
```

Action Text renders embedded `<action-text-attachment>` elements by resolving
their sgid attribute of the element into an instance. Once resolved, that
instance is passed along to a render helper. As a result, the HTML is embedded
as a descendant of the `<action-text-attachment>` element.

To be rendered within Action Text `<action-text-attachment>` element as an
attachment, we must include the `ActionText::Attachable` module, which
implements `#to_sgid(**options)` (made available through the
`GlobalID::Identification` concern).

You can also optionally declare `#to_attachable_partial_path` to render a custom
partial path and `#to_missing_attachable_partial_path` for handling missing
records.

An example can be found here:

```ruby
class Person < ApplicationRecord
  include ActionText::Attachable
end

person = Person.create! name: "Javan"
html = %Q(<action-text-attachment sgid="#{person.attachable_sgid}"></action-text-attachment>)
content = ActionText::Content.new(html)
content.attachables # => [person]
```

### Rendering an Action Text Attachment

The default way that an `<action-text-attachment>` is rendered is through the
default path partial.

To illustrate this further, let’s consider a User model:

```ruby
# app/models/user.rb
class User < ApplicationRecord
  has_one_attached :avatar
end

user = User.find(1)
user.to_global_id.to_s #=> gid://MyRailsApp/User/1
user.to_signed_global_id.to_s #=> BAh7CEkiCG…
```

NOTE: We can mix `GlobalID::Identification` into any model with a `.find(id)`
class method. Support is automatically included in Active Record.

The above code will return our identifier to uniquely identify a model instance.

Next, consider some rich text content that embeds an `<action-text-attachment>`
element that references the User instance's signed GlobalID:

```html
<p>Hello, <action-text-attachment sgid="BAh7CEkiCG…"></action-text-attachment>.</p>
```

Action Text uses the "BAh7CEkiCG…" String to resolve the User instance. It then
renders it with the default partial path when you render the content.

In this case, the default partial path is the `users/user` partial:

```html+erb
<%# app/views/users/_user.html.erb %>
<span><%= image_tag user.avatar %> <%= user.name %></span>
```

Hence, the resulting HTML rendered by Action Text would look something like:

```html
<p>Hello, <action-text-attachment sgid="BAh7CEkiCG…"><span><img src="..."> Jane Doe</span></action-text-attachment>.</p>
```

### Rendering a Different Partial for the action-text-attachment

To render a different partial for the attachable, define
`User#to_attachable_partial_path`:

```ruby
class User < ApplicationRecord
  def to_attachable_partial_path
    "users/attachable"
  end
end
```

Then declare that partial. The User instance will be available as the user
partial-local variable:

```html+erb
<%# app/views/users/_attachable.html.erb %>
<span><%= image_tag user.avatar %> <%= user.name %></span>
```

### Rendering a Partial for an Unresolved Instance or Missing action-text-attachment

If Action Text is unable to resolve the User instance (for example, if the
record has been deleted), then a default fallback partial will be rendered.

To render a different missing attachment partial, define a class-level
`to_missing_attachable_partial_path` method:

```ruby
class User < ApplicationRecord
  def self.to_missing_attachable_partial_path
    "users/missing_attachable"
  end
end
```

Then declare that partial.

```html+erb
<%# app/views/users/missing_attachable.html.erb %>
<span>Deleted user</span>
```

### Attachable via API

If your architecture does not follow the traditional Rails server-side rendered
pattern, then you may perhaps find yourself with a backend API (for example,
using JSON) that will need a separate endpoint for uploading files. The endpoint
will be required to create an `ActiveStorage::Blob` and return its
`attachable_sgid`:

```json
{
  "attachable_sgid": "BAh7CEkiCG…"
}
```

Thereafter, you can take the `attachable_sgid` and insert it in rich text
content within your frontend code using the `<action-text-attachment>` tag:

```html
<action-text-attachment sgid="BAh7CEkiCG…"></action-text-attachment>
```

## Miscellaneous

### Avoiding N+1 Queries

If you wish to preload the dependent `ActionText::RichText` model, assuming your
rich text field is named `content`, you can use the named scope:

```ruby
Article.all.with_rich_text_content # Preload the body without attachments.
Article.all.with_rich_text_content_and_embeds # Preload both body and attachments.
```