Rich text é um registro polimórfico

Prévia grátis 4 min de leitura

Todo model Rails que guarda texto simples o mantém numa coluna da própria tabela: uma coluna body, uma coluna description, uma por campo. O ActionText faz diferente. Ele guarda conteúdo rico numa única tabela compartilhada, chamada action_text_rich_texts, e liga cada linha ao seu dono por uma associação polimórfica.

A tabela por trás de todo has_rich_text

Rode bin/rails db e descreva a tabela com \d. As colunas, em resumo:

\d action_text_rich_texts
Coluna      | Tipo     | Notas
------------|----------|-------------------------------
id          | bigint   | chave primária
name        | varchar  | nome do atributo ("content")
body        | text     | o HTML de fato
record_type | varchar  | "Community::Comment"
record_id   | bigint   | id do comentário
created_at  | datetime |
updated_at  | datetime |

Três colunas fazem o trabalho. name é o atributo declarado no model: quando você escreve has_rich_text :content, a string "content" cai aqui. record_type e record_id formam a chave estrangeira polimórfica, que aponta de volta para o model dono desse rich text.

Dois models, uma tabela

Esta aplicação tem dois models que usam ActionText. O primeiro é Enrollment::LessonNote:

# app/models/enrollment/lesson_note.rb (trimmed)
module Enrollment
  class LessonNote < ApplicationRecord
    has_rich_text :content
  end
end

O segundo é Community::Comment:

# app/models/community/comment.rb (trimmed)
module Community
  class Comment < ApplicationRecord
    has_rich_text :content
  end
end

Os dois chamam has_rich_text :content e guardam o rich text na mesma tabela action_text_rich_texts. A chave polimórfica os separa: o rich text de uma nota tem record_type: "Enrollment::LessonNote", e o de um comentário tem record_type: "Community::Comment".

O que o has_rich_text cria

Chamar has_rich_text :content num model faz três coisas por baixo dos panos:

  1. Define uma associação has_one, has_one :rich_text_content, class_name: "ActionText::RichText", e o model ganha um acessor .content que devolve um registro ActionText::RichText, não uma string.
  2. Define um leitor content, que monta o registro quando ainda não existe, e um escritor content=, que define o body dele, então note.content = "Olá" se lê como um atributo comum.
  3. Declara esse has_one com autosave: true e dependent: :destroy, então salvar o pai salva o rich text, e destruir o pai o destrói.

Você nunca mexe na tabela action_text_rich_texts diretamente. Você escreve note.content = "<p>Ideia em negrito</p>", salva a nota, e o ActionText cuida da ligação.

Leitura: o ActionText::Content embrulha o HTML

Ler comment.content não devolve uma string crua. Devolve um registro ActionText::RichText cujo atributo .body é um objeto ActionText::Content. Esse objeto embrulha o HTML bruto e oferece helpers:

comment = Community::Comment.first
comment.content              # => registro ActionText::RichText
comment.content.body         # => objeto ActionText::Content
comment.content.to_s         # => o HTML, dentro de <div class="trix-content">
comment.content.to_plain_text # => "Ideia em negrito" (sem tags)

A diferença importa na validação. O Community::Comment desta aplicação valida o tamanho no texto puro:

def content_present_and_within_limit
  text = content.present? ? content.to_plain_text.strip : ""
  return errors.add(:content, :blank) if text.empty? && !content&.body&.attachments&.any?
  errors.add(:content, :too_long, count: MAX_LENGTH) if text.length > MAX_LENGTH
end

Validar to_plain_text.length em vez do tamanho do HTML bruto é proposital. Um par de negrito, <strong></strong>, acrescenta 17 caracteres de markup que o estudante nunca digitou e que não deveriam contar contra o limite.

A migration que você já rodou

O ActionText traz um gerador de instalação (bin/rails action_text:install) que cria a migration. A versão desta aplicação fica em db/migrate/20260830180001_create_action_text_tables.action_text.rb. Ela cria uma tabela, action_text_rich_texts, com o índice único em [record_type, record_id, name] que garante um rich text por nome por registro.

Para onde este curso vai

Esta primeira aula tratou da camada de armazenamento, a tabela polimórfica que guarda todo rich text da aplicação. As seis aulas seguintes sobem a partir daqui:

  • Aula 2: o editor Trix, o que ele renderiza e como o rich_text_area liga o formulário ao armazenamento.
  • Aula 3: sanitização, ou seja, quais tags HTML o ActionText mantém e quais descarta.
  • Aula 4: anexos, e como o Trix e o ActiveStorage trabalham juntos para embutir arquivos.
  • Aula 5: renderização e estilo, exibindo rich text com segurança e com boa aparência.
  • Aula 6: a troca de editor, e como esta aplicação usa o Lexxy no lugar do Trix sobre a mesma camada de armazenamento.
  • Aula 7: juntando tudo, o ciclo completo da requisição, da tecla digitada à página renderizada.
Aula 1 de 7 · grátis

Continue com a aula 2

Trix: o editor padrão

O Trix renderiza um elemento customizado que escreve um conjunto fixo de tags HTML num input escondido.

Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.

Digite para buscar...