Continuando a série

No post anterior cobrimos a API central da ElementComponent: criação de elementos, DSL de blocos e atributos. Agora vamos aos recursos que tornam a gem viável para uso real em produção: hooks de renderização, segurança contra XSS, cache e integração com Rails.

Hooks de renderização

A ElementComponent oferece três hooks que rodam ao redor do ciclo de render: before_render, after_render e around_render. Eles permitem alterar o elemento dinamicamente antes de virar HTML — por exemplo, adicionar uma classe condicional apenas no momento da renderização:

div = ElementComponent::Element.new("div")
div.define_singleton_method(:before_render) { add_attribute(class: "dynamic") }
div.add_content("content")
puts div.render
# => <div class="dynamic">content</div>

Esse padrão é a base de vários componentes internos da gem, que usam before_render para montar as classes CSS finais (por exemplo, combinar variant + outline + size num Button) só quando o render é de fato chamado.

Escaping automático e proteção contra XSS

Todo conteúdo string passado para add_content é escapado automaticamente. Caracteres como <, >, & e " viram suas entidades HTML:

el = ElementComponent::Element.new("p")
el.add_content("<script>alert('xss')</script>")
puts el.render
# => <p>&lt;script&gt;alert('xss')&lt;/script&gt;</p>

Isso é importante porque, diferente de simplesmente interpolar strings, a ElementComponent trata todo conteúdo textual como não confiável por padrão — um comportamento seguro por definição.

Quando você realmente precisa injetar HTML sem escaping (por exemplo, HTML já sanitizado por outra camada), use ElementComponent.html_safe():

el.add_content(ElementComponent.html_safe("<b>bold</b>"))
puts el.render
# => <p><b>bold</b></p>

O opt-out é explícito — você precisa marcar deliberadamente o conteúdo como seguro, o que evita escapes acidentais de XSS por esquecimento.

Cache

Para elementos com conteúdo caro de gerar, a gem oferece cache embutido:

el = ElementComponent::Element.new("div")
el.add_content(expensive_operation)
el.cache                        # habilita cache em memória
el.render                       # calcula e armazena em cache
el.render                       # retorna o HTML já em cache
el.expire_cache!                # limpa o cache

Quando Rails.cache está disponível, basta passar expires_in para usar o backend de cache do Rails em vez do cache em memória do processo:

el.cache(expires_in: 5.minutes) # usa Rails.cache quando disponível

Isso é útil para componentes reutilizados em múltiplas páginas, como um menu de navegação ou um card que muda pouco entre requisições.

Integração com Rails

A ElementComponent não depende do Rails, mas se integra bem a ele. Definindo view_context, os componentes ganham acesso a helpers como link_to, image_tag e rotas nomeadas via method_missing:

component = ElementComponent::Alert.new(variant: :success)
component.view_context = helpers
component.add_content(component.link_to("Home", root_path))
component.render
# => <div class="alert alert-success" role="alert"><a href="/">Home</a></div>

Para habilitar isso globalmente em todos os elementos da aplicação, basta incluir o módulo RailsHelpers na classe base:

ElementComponent::Element.include(ElementComponent::RailsHelpers)

Isso é especialmente útil ao construir componentes Bootstrap (que veremos nos próximos posts) que precisam gerar links internos usando as rotas da aplicação, em vez de URLs hardcoded.

Próximo post

Com a base coberta — construção de elementos, hooks, segurança e cache — a próxima parte da série entra nos 17 componentes Bootstrap 5 prontos para uso que vêm embutidos na gem, começando por Alert, Badge, Button, ButtonGroup e Card.