Volver

Deje su comentario abajo

Publicado el 2025-12-28


Continuando con las mejoras paso a paso a mi Blog, lo siguiente es dar un espacio para que otras personas comenten. Para esto hay varias opciones, desde usar un servicio de terceros, hasta implementar yo mismo toda la lógica.

Lo primero que pensé fue en usar Disqus ya que lo había visto en varios blogs que sigo, y puedes usar el mismo usuario en distintas páginas, generando una suerte de conexión inter-blog. Lamentablemente, Disqus es una opción pagada (al menos $11 dólares al mes), y la opción gratuita es en base a anuncios, algo que jamás agregaré a mi blog.

Descartando esta opción me puse a pensar en la opción opuesta: encargarme yo mismo la implementación. En teoría no es algo tan difícil —de hecho, es apenas más complicado que las publicaciones de este blog— pero algo me frena llegar y aplicar el "Hágalo Usted Mismo": Rails está lleno de features pre-hechas y expresiones idiomáticas con las que no estoy familiarizado, y te insta a hacer las cosas "a la manera Rails". Por esto, decidí buscar en internet si Rails ya tenía incorporada la funcionalidad, o si había una forma más "Rails" de hacerlo.

Implementando un modelo de comentarios

Buscando cómo implementar los comentarios me topé con el excelente blog de Jon Sully y en particular, su publicación donde explica cómo implementar comentarios de forma sencilla, incluyendo un filtro anti-spam basado en IA. Es tan buena su publicación que me costó pensar en algo que añadirle para escribir la mía —además de la traducción, que en verdad cualquiera puede hacer con un click—.

Sin embargo, la implementación de Jon tiene una diferencia clave con la mía: las publicaciones de Jon son estáticas —viven en archivos alojados en su proyecto— mientras que las mías son ActiveRecords que viven en mi base de datos y funcionan con toda la lógica de manejo de modelos de Rails. Eso hace que mi caso de uso sea mucho más sencillo, pues no necesito simular las relaciones entre los comentarios (comments) y las publicaciones (posts). Usando el comando bin/rails generate model Comment name:string email:string body:text spam?:boolean post:belongs_to la migración queda entonces así:

class CreateComments < ActiveRecord::Migration[8.1]
def change
create_table :comments do |t|
t.string :name, null: false
t.string :email, null: false
t.text :body, null: false
t.boolean :spam?, null: false
t.belongs_to :post, null: false, foreign_key: true

t.timestamps
end

add_index :comments, :created_at
end
end

Los null: false (agregados a mano) se aseguran a nivel de base de datos que estos campos nunca estén vacíos. El indizar created_at con add_index hará que el ordenar los comentarios por fecha sea más rápido. El comando anterior agregó automáticamente la línea belongs_to :post al modelo de Comment, y a mano le agrego un scope (pequeña query reutilizable) para obtener los comentarios más viejos primero: scope :oldest_first, -> { order(created_at: :asc) }. Al modelo de Post le agregué manualmente la línea has_many :comments, para poder referenciar los comentarios desde la publicación.

Implementando el controlador

La siguiente pieza a implementar es el Controlador, el cual maneja las peticiones hacia el Modelo y recoge datos para la Vista. Como los comentarios se muestran sólamente en las publicaciones y no en su propia vista, sólo hay que agregar un endpoint para crear nuevos comentarios (POST /comments), lo que en Rails se logra definiendo la función create. El comando bin/rails generate controller Comments create facilita crear la base. Hay que tener cuidado eso sí, porque este comando agrega en config/routes.rb un endpoint GET en vez de POST, por lo que hay que corregirlo. Luego, el contenido final del Controlador será:

class CommentsController < ApplicationController
def create
post = Post.find(params[:comment][:post_id])
comment = post.comments.new(comment_params)
comment.spam = OpenAi.comment_is_spam?(comment.name, comment.email, comment.body, post.body)
comment.save!
redirect_to post, status: :see_other
end

private def comment_params
params.require(:comment).permit(:name, :email, :body)
end
end

La detección de spam la implementé tal y como indica Jon Sully en su publicación. Sólo tuve que redactar mi propio prompt para que ChatGPT decida qué comentarios considerar como spam y cuáles no. Igual como hizo Jon, no incluiré acá el prompt para evitar vulnerabilidades.

Agregando soporte Markdown

Habiendo creado el Modelo y el Controlador, el último paso es la Vista. Acá me desviaré un poco, ya que Jon incluyó una característica que desde un principio he querido para este Blog: soporte markdown. Markdown es mi forma favorita para escribir, ya que es muy fácil agregar cosas como links (usando [texto](url)) y dar formato a texto (por ejemplo, usando # para crear encabezados de diferente nivel) sin tener que sacar las manos del teclado para clickear en una interfaz, y en general no tener que lidiar con las ambigüedades y caracteres invisibles de los editores WYSIWYG (como Word, o el que trae por defecto Rails), preferiendo lo explícito de los editores WYSIWYM. Además, los archivos .md son meros archivos de texto, así que se los puede editar en cualquier parte (incluso desde un terminal), hacer seguimiento en un repositorio git, entre otras ventajas.

Para agregar soporte markdown instalé la gema redcarpet con el comando bundle add redcarpet (para que agregue automáticamente la línea en el Gemfile, a la vez que la instala en mi computador). Luego, agregué una función de ayuda en helpers/application_helper.rb para renderizar el Markdown como HTML:

def markdown(text)
return "" if text.blank?

renderer = Redcarpet::Markdown.new(
Redcarpet::Render::HTML,
filter_html: true,
autolink: true
)

sanitize(renderer.render(text))
end

Esta función será usada en la Vista a continuación.

Implementando la vista de los comentarios

Para implementar la Vista, sólo necesitamos mostrar los comentarios en nuestra vista de publicaciones (views/posts/show.html.erb) y agregar un formulario para poder crear nuevos comentarios. Creé el formulario dentro de un Partial para dejarlo más ordenado. El que Jon muestra en su publicación está incompleto; la versión que usé yo es esta:

<%# app/views/comments/_form.html.erb %>
<%= form_with model: comment do |form| %>
<% if form.object.errors.any? %>
<p class="error"><%= form.object.errors.full_messages.first %></p>
<% end %>

<%= form.hidden_field :post_id %>

<div>
<%= form.label :name %>
<%= form.text_field :name, required: true %>
</div>

<div>
<%= form.label :email %>
<%= form.email_field :email, required: true %>
</div>

<div>
<%= form.label :body %>
<%= form.text_area :body, required: true %>
</div>

<div>
<%= form.submit %>
</div>
<% end %>

Este formulario es renderizado desde posts/show.html.erb usando <%= render "comments/form", comment: @comment %>, habiendo previamente inicializado @comment = Comment.new(post: @post) en el Controlador de posts. Después de eso, los comentarios se muestran en la vista usando:

<% if @post.comments.empty? %>
<h3>Aún no hay comentarios en esta publicación</h3>
<% else %>
<div class="comments">
<% @post.comments.where(spam: false).oldest_first.each do |comment| %>
<div class="comment">
<div class="comment-date"><%= comment.created_at.strftime('%Y-%m-%d') %></div>
<div class="comment-name"><%= comment.name %></div>
<div class="comment-body"><%= markdown(comment.body) %></div>
</div>
<% end %>
</div>
<% end %>

El problema es que el formulario de nuevos comentarios se ve muy feo, y los comentarios se ven peor. De hecho, alguien que comente puede abusar del Markdown para hacer sus comentarios gigantes —a no ser que lo detecte el filtro de spam—.

Los comentarios y el formulario se ven horribles por defecto.

El lado positivo es que sí están funcionando los comentarios. Voy a abordar cada uno de estos temas individualmente.

Internacionalización (i18n)

Rails tiene soporte nativo de i18n, así que me basé en la guía para corregir las etiquetas en inglés.

Primero debo setear la configuración por defecto de i18n. Esto se hace en el archivo config/application.rb.

module Blog
class Application < Rails::Application
# ...
config.time_zone = "Santiago" # Para usar la hora local de Chile
config.i18n.available_locales = [:es] # Para que acepte el idioma español
config.i18n.default_locale = :es # Idioma español por defecto
end
end

Luego, lo único que hay que hacer es crear un diccionario para el idioma español en config/locales, y setear valores para los atributos de comment.

# es.yml
es:
activerecord:
attributes:
comment:
name: Nombre
email: Email
body: Comentario
helpers:
submit:
comment:
create: Enviar comentario

Separación y estilo de los comentarios

Como ya mencioné en mi publicación anterior, el front no es mi fuerte. Primero intenté corregir los estilos por mi cuenta, y cuando no pude, hice trampa le pedí ayuda a Google o a la IA.

Lo primero que se me ocurrió fue inspeccionar el HTML de mi Blog para ver qué elementos y clases usa Rails para renderizar la publicación, el formulario y los comentarios. La publicación está en un <div class="trix-content">, el formulario en un <form> con sus respectivos <label> e <input> por cada campo, y los comentarios están tal cual sin tags, por lo que modifiqué la vista posts/show.html.erb para darle sus propios tags <div> con class comment, comment-name, comment-date y comment-body.

Con estas clases definidas es fácil definir márgenes y bordes para separar los distintos elementos. Usé bordes de 2px alrededor de los comentarios para individualizarlos, y reduje el espaciado en los encabezados para que se vieran mejor en los comentarios. Teniendo esos bordes los comentarios ya no se confunden con la publicación, así que decidí no modificar su estilo.

.comment-form {
margin-bottom: 2.5em;
}

.comment {
border: 2px dotted var(--border);
padding: 1.5em;
margin-bottom: 1.5em;
}

.comment:last-child {
margin-bottom: 0;
}

.comment-name {
display: inline-block;
font-size: 1.2em;
font-weight: bold;
}

.comment-date {
color: var(--muted);
font-size: 0.8em;
}

.comment-body > :is(h1, h2, h3, h4, h5, h6) {
margin-top: 1em;
}

Estilo del formulario de comentarios

El secreto para hacer que las cajas del formulario para dejar comentarios se vean bien (según la IA) es: 1) darles width: 100% para que ocupen todo el ancho del body; 2) darles padding: 2px para que haya espacio entre el texto y el recuadro; 3) aplicarle color al texto, fondo y al borde. Este último es el más importante, y hace la diferencia entre una interfaz estilo "Web 1.0" y algo más moderno.

.input-field {
width: 100%;
padding: 8px;
background-color: var(--surface);
color: var(--fg-emphasis);
border: 1px solid var(--border);
}

Palabras de cierre y próximos pasos del blog

Escribí esta publicación de forma secuencial, pero en el camino modifiqué varias cosas que me pareció innecesario detallar:

Si alguien a futuro usa esta publicación de guía para armar sus comentarios, le recomiendo tratar de inventar su propia solución, probar distintas alternativas y elegir la que resulte en una mejor experiencia. Sino, ahora que se pueden hacer comentarios en el Blog, pueden dejar sus preguntas abajo y con gusto les responderé.

Sobre la próxima funcionalidad a implementar, lo que quería era habilitar la suscripción por correo. Configuré todos los registros DNS para demostrarle a AWS que soy dueño de mi dominio, pero rechazaron mi solicitud para tener Production Access en SES (su servicio de email). Aprovechando que ahora se pueden dejar comentarios, me gustaría escuchar de ustedes. ¿Qué servicio recomiendan para enviar subscripciones de correo?

Muchas gracias por leerme. ¡Feliz año nuevo a todos!

—Claudio Yáñez M.

---

EDIT 2025/12/30
Pedro Correa me avisó que no podía dejar comentarios porque le pedía estar loggeado. Fue porque me faltó agregar la línea allow_unauthenticated_access only: :create al CommentsController . De a poco se van aprendiendo las mañas de Rails. 😅

Deja tu comentario

2025-12-31
Pedro Correa

Excelente avance en el blog, saludos Claudio!