Se eu te dissesse que você pode ter comentários em tempo real no seu projeto Rails sem instalar React, Vue ou qualquer outro framework frontend pesado, você acreditaria? Pois é exatamente isso que fizemos na Live 56. A proposta foi simples: usar apenas o que o Rails moderno já entrega de fábrica — Turbo Frames, Turbo Streams e ActionCable — e criar um sistema prático e profissional.
O objetivo não era só mostrar que é possível, mas também como essas ferramentas conversam entre si para gerar uma experiência reativa, sem complicar o código.
Neste passo a passo, vamos reproduzir a implementação da live considerando que você já tenha:
Rails 7.2 ou superior.
solid-cable configurado.
Model
Postrepresentando postagens.Devise configurado com o model
User.
1. Crie o model
Aqui vamos criar um modelo para armazenar os comentários, relacionando-os tanto ao Post quanto ao User.
rails g model comment content:text post:references user:references
rails db:migrate2. Criar o controller de comentários
Aqui está o fluxo clássico: pegamos o post, construímos o comentário com o usuário autenticado. Não precisaremos nos preocupar com a resposta, pois a frente o Action Cable fará esse trabalho por nós.
class CommentsController < ApplicationController
before_action :load_post
before_action :authenticate_user!, only: %i[create destroy]
def index
@comments = @post.comments.includes(:user).order(created_at: :asc)
@new_comment = @post.comments.new
end
def create
@comment = @post.comments.new(comment_params)
@comment.user = current_user
@comment.save
render json: {}, status: :no_content #Não vamos usar o retorno
end
def destroy
@comment = @post.comments.where(user_id: current_user.id).find(params[:id])
@comment.destroy
redirect_to post_comments_path(@post)
end
private
def comment_params
params.require(:comment).permit(:content)
end
def load_post
@post = Post.published.find_by!(slug: params[:post_id])
end
end3. Ajuste as rotas
Aninhamos comments dentro de posts para que a URL reflita que um comentário sempre pertence a um post.
resources :posts, path: "conteudos", only: %i[index show] do
resources :comments, only: %i[index create destroy]
end4. Configure a Interface
Usamos lazy loading para carregar os comentários apenas quando necessário, melhorando a performance. Na view que representa o show do seu Post adicione a seguinte tag para carregar os comentários relacionados.
<%= turbo_frame_tag "comments-container", src: post_comments_path(@post), loading: "lazy" %>Depois disso, é hora de criar a view principal gerencia tanto a listagem quanto o formulário. Essa será o comments/index.html.erb.
<%= turbo_frame_tag "comments-container" do %>
<section class="container">
<% if @comments.any? %>
<h2>Comentários</h2>
<div class="row">
<div id="comments">
<%= render @comments %>
</div>
</div>
<% else %>
<h2>Nenhum Comentário Ainda</h2>
<div class="row">
<div id="comments">
<p class="text-muted">Seja o primeiro a comentar!</p>
</div>
</div>
<% end %>
<%= turbo_stream_from @post, "comments" %>
<%= render partial: "comments/form", locals: { post: @post, comment: @new_comment } %>
<% end %>Isso é o que faz os clientes receberem as atualizações em tempo real: <%= turbo_stream_from @post, "comments" %>. Sem isso o ActionCable não será conectado a sua inteface.
Para permitir a inserção de novos comentários vamos criar um formulário que se adapta ao estado de autenticação do usuário, usando SimpleForm e nosso editor Marksmith no arquivo comments/_form.html.erb.
<%= turbo_frame_tag "new_comment" do %>
<div class="card bg-secondary">
<div class="card-body">
<% if user_signed_in? %>
<h2>Deixe seu comentário</h2>
<%= simple_form_for [post, comment], html: { class: "row gy-4" } do |f| %>
<div class="col-12">
<%= f.marksmith :content %>
</div>
<div class="col-12">
<%= f.button :button, "Publicar", class: "btn btn-lg btn-primary" %>
</div>
<% end %>
<% else %>
<h2>Deixe seu comentário</h2>
<p class="text-muted mb-4">Se você quiser comentar, por favor faça o login.</p>
<% end %>
</div>
</div>
<% end %>Para concluir, cada comentário tem seu partial com o conteúdo, nome de quem publicou e a opção de remoção, quando for criado pelo próprio usuário logado. Sendo assim, crie o arquivo comments/_comment.html.erb.
<div>
<h6><%= comment.user.name %>
<span class="fs-sm text-muted"><%= l(comment.created_at, type: :short) %></span>
<% if comment.user_id == current_user&.id %>
<%= button_to post_comment_path(comment.post, comment), method: :delete, data: { turbo_confirm: "Tem certeza que deseja remover este comentário?" } do %>
Remover
<% end %>
<% end %>
<%== marksmithed comment.content %>
</div>5. Configure o model
O segredo está no after_create com broadcast_append_to. Assim, sempre que um comentário novo é criado, ele é transmitido automaticamente para todos os clientes conectados no frame específico (identificado pelo post que faz parte).
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
after_create :notify_post
def notify_post
broadcast_append_to(
[ post, "comments" ],
target: "comments",
partial: "comments/comment",
locals: { comment: self, current_user: Current.user }
)
end
endPerceba que passamos o current_user no locals, pois devido a forma como o ActionCable funciona ele não consegue reconhecer o usuário do Devise. Mas não afeta o funcionamento do carregamento comum via navegação.
Por que isso funciona tão bem
O Turbo Frame isola e atualiza apenas a área necessária.
O Turbo Stream transmite as mudanças para todos conectados.
O ActionCable (com
solid-cable) cuida da conexão WebSocket.
Resultado? Comentários instantâneos, sem recarregar a página e sem dependências externas.
E o melhor: tudo 100% Rails, mantendo seu projeto simples e fácil de manter.
Se quiser ver esse exemplo em ação com explicação ao vivo, confira a Live 56, logo no início dessa página ou pelo YouTube.
Ainda não há comentários. Seja o primeiro a comentar!