Uso de cookies em comunidade.academiadoruby.com.br

Utilizamos cookies para melhorar sua experiência. Você pode aceitar ou recusar o uso de cookies não essenciais. Sua escolha ficará salva por 6 meses. Saiba mais em Política de Privacidade · Política de Cookies

  1. Conteúdos
  2. Hotwire

Como Criar Comentários em Tempo Real no Rails Usando Turbo e ActionCable (Sem React)

Daniel Denis Moreira

· 5 min de leitura

Aula Prática

Acompanhe o vídeo acima para melhor compreensão do conteúdo.

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 Post representando 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:migrate

2. 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
end

3. 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]
end

4. 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
end

Perceba 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.


Tópicos Relacionados
Compartilhar

Escrito por Daniel Denis Moreira

Criador da Academia do Ruby.
Acredito que simplicidade é estratégia — e que Rails é uma vantagem competitiva.

Feedback

Esse conteúdo foi…

Comentários (0)

Ainda não há comentários. Seja o primeiro a comentar!

Faça login para deixar um comentário.

Conteúdos Relacionados