Skip to content

Instantly share code, notes, and snippets.

@secretpray
Last active October 5, 2023 06:56
Show Gist options
  • Save secretpray/53bcf0272295fb2981dae07ee136069c to your computer and use it in GitHub Desktop.
Save secretpray/53bcf0272295fb2981dae07ee136069c to your computer and use it in GitHub Desktop.
Autorize broadcast object (method 1) Rails 6-7 Turbo

Model Comment

class Comment < ApplicationRecord
  belongs_to :user
  has_rich_text :content

  after_create_commit do
    broadcast_append_to :comments, target: "comments", partial: "comments/comment_for_stream", locals: { comment: self }
    update_counter
  end

  def render_not_allowed(user, flash)
     broadcast_update_to([user, :comments], target: :notifications, partial: 'shared/flash', locals: { flash: flash })
  end

  private

  def update_counter
    broadcast_update_to :comments, target: "comments_counter", partial: 'comments/count', locals: { count: Comment.count }
  end
end

CommentsController

 def create
    @comment = current_user.comments.new(comment_params)
    respond_to do |format|
      if @comment.save
        format.turbo_stream {
          render turbo_stream: turbo_stream.replace("new_comment",
                                                     partial: "comments/form",
                                                    locals: { comment: Comment.new }
          )
        }
      else
        format.html { render :new, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    if current_user.id != @comment.user.id
      flash.now[:alert] = "Not allowed!"
      @comment.render_not_allowed(current_user, flash)
      return
    end

    @comment.destroy
  end

views/comments/_comment_for_stream.html.erb (for broadcasting)

<%#= content_tag :div, id: dom_id(comment), class: "card mb-3" do %>
<%= turbo_frame_tag dom_id(comment), class: "card mb-3" do %>

  <div class="card-body">

    <%= render 'comments/button_section', comment: comment %>

    <p class="card-text"><%= comment.content %></p>
    <p id='<%= dom_id(comment)%>_count'>
      <%= comment.vote_count %>
    </p>

    <%= render 'comments/votes_link', comment: comment, static: true %>

  </div>
<% end %>

views/comments/_button_section.html.erb

<div class="d-flex justify-content-between align-items-center">
  <div class="d-flex">
    <span class="card-title fs-6 fw-lighter">
      <%= time_ago_in_words(comment.created_at) %> ago by &nbsp;
    </span>
    <span class='fw-light'>
      <%= comment.user.email.split('@').first %>
    </span>
  </div>
  <div class="d-flex">
    <div class="">
      <%= link_to "Show", comment,
                  class: 'btn btn-link text-decoration-none fw-light',
                  data: {turbo_frame: :comments } %>
    </div>
    <#% Stimulus controller connected! %>
    <div class="d-flex d-none"
        data-controller='check-author'
        data-check-author-author-id-value='<%= comment.user.id %>' >
      <%= link_to 'Edit', edit_comment_path(comment),
                          data: { turbo_frame: dom_id(comment) },
                          class: 'btn btn-link text-decoration-none fw-light' %>
      <%= button_to comment,
                    method: :delete,
                    form: { data: { turbo_confirm: 'Are you sure?' }},
                    # data: { turbo_method: :delete,
                    #         turbo_confirm: "Are you sure?" },
                    id: 'close-custom',
                    class: 'btn-close btn-close-white mt-2',
                    data: { turbo_frame_target: :_top } do %>

      <% end %>
    </div>
  </div>
</div>

javascript/controllers/check_author_controller.js

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="check-author"
export default class extends Controller {
  static values = {
    authorId: String
  }

  connect() {
    if (this.currentUserId === this.authorId) {
      this.element.classList.remove("d-none")
    }
  }

  get authorId() {
    return this.authorIdValue
  }

  get currentUserId() {
    return document.querySelector("[name='current-user-id']").content
  }
}

views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Turbo Comments</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="current-user-id" content="<%= current_user&.id %>">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
..other code..

javascript/controllers/index.js

import CheckAuthorController from "./check_author_controller.js"
application.register("check-author", CheckAuthorController)
@secretpray
Copy link
Author

secretpray commented Oct 5, 2023

Alternative

  1. We send the same partial to everyone. For the user, we open a personal channel in addition to the general one. In this channel, we send the author's partial. It sounds simple, but there is a race condition. We cannot guarantee that the author's broadcast will arrive later than the general one. We need to introduce a delay, but even that cannot always guarantee the correct order.

  2. If there aren't many users, like in a chat, you can simply get a list of all participants except the author, send them the general partial, and for the author, render without a broadcast in Turbo Stream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment