1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
|
**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. See [the Trix
editor website](https://trix-editor.org/) for examples.
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 [`ActionText::RichText`
documentation](https://api.rubyonrails.org/classes/ActionText/RichText.html).
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.
```
|