Descripción General de Action Text

Esta guía te proporciona todo lo que necesitas para comenzar a manejar contenido de texto enriquecido.

Después de leer esta guía, sabrás:


1 ¿Qué es Action Text?

Action Text facilita el manejo y la visualización de contenido de texto enriquecido. El contenido de texto enriquecido es texto que incluye elementos de formato como negritas, cursivas, colores e hipervínculos, proporcionando una presentación visualmente mejorada y estructurada más allá del texto simple. Nos permite crear contenido de texto enriquecido, almacenarlo en una tabla y luego adjuntarlo a cualquiera de nuestros modelos.

Action Text incluye un editor WYSIWYG llamado Trix, que se utiliza en aplicaciones web para proporcionar a los usuarios una interfaz amigable para crear y editar contenido de texto enriquecido. Maneja todo, desde proporcionar capacidades enriquecedoras como el formato del texto, agregar enlaces o citas, incrustar imágenes, y mucho más. Consulta el sitio web del editor Trix para ver ejemplos.

El contenido de texto enriquecido generado por el editor Trix se guarda en su propio modelo RichText que puede asociarse con cualquier modelo Active Record existente en la aplicación. Además, cualquier imagen incrustada (u otros adjuntos) se puede almacenar automáticamente usando Active Storage (que se añade como una dependencia) y asociarse con ese modelo RichText. Cuando es momento de renderizar contenido, Action Text procesa el contenido sanitizándolo primero para que sea seguro incrustarlo directamente en el HTML de la página.

La mayoría de los editores WYSIWYG son envoltorios alrededor de las APIs contenteditable y execCommand de HTML. Estas APIs fueron diseñadas por Microsoft para soportar la edición en vivo de páginas web en Internet Explorer 5.5. Eventualmente fueron ingeniería inversa y copiadas por otros navegadores. En consecuencia, estas APIs nunca fueron completamente especificadas o documentadas, y debido a que los editores HTML WYSIWYG son enormes en alcance, la implementación de cada navegador tiene su propio conjunto de errores y peculiaridades. Por lo tanto, los desarrolladores de JavaScript a menudo tienen que resolver las inconsistencias.

Trix evita estas inconsistencias tratando a contenteditable como un dispositivo de E/S: cuando la entrada llega al editor, Trix convierte esa entrada en una operación de edición en su modelo de documento interno, luego vuelve a renderizar ese documento en el editor. Esto le da a Trix control completo sobre lo que sucede después de cada pulsación de tecla y evita la necesidad de usar execCommand y las inconsistencias que vienen con él.

2 Instalación

Para instalar Action Text y comenzar a trabajar con contenido de texto enriquecido, ejecuta:

$ bin/rails action_text:install

Hará lo siguiente:

  • Instala los paquetes JavaScript para trix y @rails/actiontext y los añade a application.js.
  • Añade la gema image_processing para el análisis y las transformaciones de las imágenes incrustadas y otros adjuntos con Active Storage. Por favor, consulta la guía Descripción General de Active Storage para más información al respecto.
  • Añade migraciones para crear las siguientes tablas que almacenan contenido de texto enriquecido y adjuntos: action_text_rich_texts, active_storage_blobs, active_storage_attachments, active_storage_variant_records.
  • Crea actiontext.css y lo importa en application.css. La hoja de estilos Trix también se incluye en el archivo application.css.
  • Añade las vistas parciales por defecto _content.html y _blob.html para renderizar contenido de Action Text y adjuntos de Active Storage (también conocidos como blobs), respectivamente.

Después de esto, ejecutar las migraciones añadirá las nuevas tablas action_text_* y active_storage_* a tu aplicación:

$ bin/rails db:migrate

Cuando la instalación de Action Text crea la tabla action_text_rich_texts, utiliza una relación polimórfica para que múltiples modelos puedan añadir atributos de texto enriquecido. Esto se hace a través de las columnas record_type y record_id, que almacenan el nombre de la clase del modelo y el ID del registro, respectivamente.

Con las asociaciones polimórficas, un modelo puede pertenecer a más de un modelo diferente, en una sola asociación. Lee más sobre esto en la guía de Asociaciones de Active Record.

Por lo tanto, si tus modelos que contienen contenido de Action Text usan valores UUID como identificadores, entonces todos los modelos que usan atributos de Action Text necesitarán usar valores UUID para sus identificadores únicos. La migración generada para Action Text también necesitará ser actualizada para especificar type: :uuid para la línea de referencias del registro.

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

3 Creación de Contenido de Texto Enriquecido

Esta sección explora algunas de las configuraciones que necesitarás seguir para crear texto enriquecido.

El registro RichText contiene el contenido producido por el editor Trix en un atributo body serializado. También contiene todas las referencias a los archivos incrustados, que se almacenan usando Active Storage. Este registro luego se asocia con el modelo Active Record que desea tener contenido de texto enriquecido. La asociación se realiza colocando el método de clase has_rich_text en el modelo al que te gustaría añadir texto enriquecido.

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

NOTA: No hay necesidad de añadir la columna content a tu tabla Article. has_rich_text asocia el contenido con la tabla action_text_rich_texts que ha sido creada, y lo vincula de nuevo a tu modelo. También puedes optar por nombrar el atributo de manera diferente a content.

Una vez que hayas añadido el método de clase has_rich_text al modelo, puedes actualizar tus vistas para hacer uso del editor de texto enriquecido (Trix) para ese campo. Para hacerlo, utiliza un rich_text_area para el campo del formulario.

<%# 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 %>

Esto mostrará un editor Trix que proporciona la funcionalidad para crear y actualizar tu texto enriquecido según corresponda. Más adelante entraremos en detalles sobre cómo actualizar los estilos para el editor.

Finalmente, para asegurarte de que puedes aceptar actualizaciones desde el editor, necesitarás permitir el atributo referenciado como un parámetro en el controlador relevante:

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

Si surge la necesidad de renombrar clases que utilizan has_rich_text, también necesitarás actualizar la columna de tipo polimórfico record_type en la tabla action_text_rich_texts para las filas respectivas.

Dado que Action Text depende de asociaciones polimórficas, que a su vez implican almacenar nombres de clases en la base de datos, es crucial mantener los datos sincronizados con los nombres de clase utilizados en tu código Ruby. Esta sincronización es esencial para mantener la consistencia entre los datos almacenados y las referencias de clase en tu base de código.

4 Renderización de Contenido de Texto Enriquecido

Las instancias de ActionText::RichText pueden incrustarse directamente en una página porque ya han sanitizado su contenido para un renderizado seguro. Puedes mostrar el contenido de la siguiente manera:

<%= @article.content %>

ActionText::RichText#to_s transforma de manera segura RichText en una cadena HTML. Por otro lado, ActionText::RichText#to_plain_text devuelve una cadena que no es segura para HTML y no debe renderizarse en navegadores. Puedes aprender más sobre el proceso de sanitización de Action Text en la documentación de ActionText::RichText.

NOTA: Si hay un recurso adjunto dentro del campo content, es posible que no se muestre correctamente a menos que tengas las dependencias necesarias para Active Storage instaladas.

5 Personalización del Editor de Contenido de Texto Enriquecido (Trix)

Puede haber momentos en los que desees actualizar la presentación del editor para cumplir con tus requisitos estilísticos, esta sección guía sobre cómo hacerlo.

5.1 Eliminación o Adición de Estilos de Trix

Por defecto, Action Text renderizará el contenido de texto enriquecido dentro de un elemento con la clase .trix-content. Esto se establece en app/views/layouts/action_text/contents/_content.html.erb. Los elementos con esta clase luego son estilizados por la hoja de estilos trix.

Si deseas actualizar alguno de los estilos de trix, puedes añadir tus estilos personalizados en app/assets/stylesheets/actiontext.css.

Sin embargo, si prefieres proporcionar tus propios estilos o utilizar una biblioteca de terceros en lugar de la hoja de estilos trix por defecto, puedes eliminar trix de las directivas del preprocesador en el archivo app/assets/stylesheets/actiontext.css eliminando lo siguiente:

= require trix

5.2 Personalización del Contenedor del Editor

Para personalizar el elemento contenedor HTML que se renderiza alrededor del contenido de texto enriquecido, edita el archivo de diseño app/views/layouts/action_text/contents/_content.html.erb creado por el instalador:

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

5.3 Personalización del HTML para Imágenes y Adjuntos Incrustados

Para personalizar el HTML renderizado para imágenes incrustadas y otros adjuntos (conocidos como blobs), edita la plantilla app/views/active_storage/blobs/_blob.html.erb creada por el instalador:

<%# 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>

6 Adjuntos

Actualmente, Action Text soporta adjuntos que se suben a través de Active Storage, así como adjuntos que están vinculados a un Signed GlobalID.

6.1 Active Storage

Al subir una imagen dentro de tu editor de texto enriquecido, utiliza Action Text que a su vez utiliza Active Storage. Sin embargo, Active Storage tiene algunas dependencias que no son proporcionadas por Rails. Para usar los previsualizadores integrados, debes instalar estas bibliotecas.

Algunas, pero no todas estas bibliotecas son requeridas y dependen del tipo de subidas que esperas dentro del editor. Un error común que los usuarios encuentran al trabajar con Action Text y Active Storage es que las imágenes no se renderizan correctamente en el editor. Esto se debe generalmente a que la dependencia libvips no está instalada.

6.2 Signed GlobalID

Además de los adjuntos subidos a través de Active Storage, Action Text también puede incrustar cualquier cosa que pueda ser resuelta por un Signed GlobalID.

Un Global ID es un URI a nivel de aplicación que identifica de manera única una instancia de modelo: gid://YourApp/Some::Model/id. Esto es útil cuando necesitas un único identificador para referenciar diferentes clases de objetos.

Al usar este método, Action Text requiere que los adjuntos tengan un ID global firmado (sgid). Por defecto, todos los modelos Active Record en una aplicación Rails mezclan la preocupación GlobalID::Identification, por lo que pueden ser resueltos por un ID global firmado y, por lo tanto, son compatibles con ActionText::Attachable.

Action Text referencia el HTML que insertas al guardar para que pueda volver a renderizar con contenido actualizado más adelante. Esto hace que puedas referenciar modelos y siempre mostrar el contenido actual cuando esos registros cambian.

Action Text cargará el modelo desde el ID global y luego lo renderizará con la ruta parcial por defecto cuando renderices el contenido.

Un adjunto de Action Text puede verse así:

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

Action Text renderiza elementos <action-text-attachment> incrustados resolviendo su atributo sgid del elemento en una instancia. Una vez resuelta, esa instancia se pasa a un helper de renderizado. Como resultado, el HTML se incrusta como un descendiente del elemento <action-text-attachment>.

Para ser renderizado dentro del elemento <action-text-attachment> de Action Text como un adjunto, debemos incluir el módulo ActionText::Attachable, que implementa #to_sgid(**options) (disponible a través de la preocupación GlobalID::Identification).

También puedes declarar opcionalmente #to_attachable_partial_path para renderizar una ruta parcial personalizada y #to_missing_attachable_partial_path para manejar registros faltantes.

Un ejemplo se puede encontrar aquí:

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]

6.3 Renderización de un Adjunto de Action Text

La forma predeterminada en que se renderiza un <action-text-attachment> es a través del parcial de ruta por defecto.

Para ilustrar esto más a fondo, consideremos un modelo User:

# 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…

NOTA: Podemos mezclar GlobalID::Identification en cualquier modelo con un método de clase .find(id). El soporte se incluye automáticamente en Active Record.

El código anterior devolverá nuestro identificador para identificar de manera única una instancia de modelo.

A continuación, considera algún contenido de texto enriquecido que incrusta un elemento <action-text-attachment> que referencia el Signed GlobalID de la instancia User:

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

Action Text utiliza la cadena "BAh7CEkiCG…" para resolver la instancia User. Luego la renderiza con la ruta parcial por defecto cuando renderizas el contenido.

En este caso, la ruta parcial por defecto es el parcial users/user:

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

Por lo tanto, el HTML resultante renderizado por Action Text se vería algo así:

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

6.4 Renderización de un Parcial Diferente para el action-text-attachment

Para renderizar un parcial diferente para el adjunto, define User#to_attachable_partial_path:

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

Luego declara ese parcial. La instancia User estará disponible como la variable local user del parcial:

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

6.5 Renderización de un Parcial para una Instancia No Resuelta o Faltante de action-text-attachment

Si Action Text no puede resolver la instancia User (por ejemplo, si el registro ha sido eliminado), entonces se renderizará un parcial de respaldo por defecto.

Para renderizar un parcial de adjunto faltante diferente, define un método de nivel de clase to_missing_attachable_partial_path:

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

Luego declara ese parcial.

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

6.6 Adjunto a través de API

Si tu arquitectura no sigue el patrón tradicional de renderizado del lado del servidor de Rails, entonces quizás te encuentres con un backend API (por ejemplo, usando JSON) que necesitará un endpoint separado para subir archivos. El endpoint será necesario para crear un ActiveStorage::Blob y devolver su attachable_sgid:

{
  "attachable_sgid": "BAh7CEkiCG…"
}

Luego, puedes tomar el attachable_sgid e insertarlo en contenido de texto enriquecido dentro de tu código frontend usando la etiqueta <action-text-attachment>:

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

7 Varios

7.1 Evitar Consultas N+1

Si deseas precargar el modelo dependiente ActionText::RichText, suponiendo que tu campo de texto enriquecido se llame content, puedes usar el scope nombrado:

Article.all.with_rich_text_content # Preload del cuerpo sin adjuntos.
Article.all.with_rich_text_content_and_embeds # Preload tanto del cuerpo como de los adjuntos.

Comentarios

Se te anima a ayudar a mejorar la calidad de esta guía.

Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.

También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.

Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.

Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.