GGrassiant
GGrassiant

Reputation: 119

Rails Hotwire actioncable stream in nested turbo_frame_tag

Following an article I am trying out Rails Hotwire and want to create a simple blog that works like a single-page app (i.e. using turbo_stream to crud posts and comments without reloading the entire page). I would also like to play a bit with actioncable at the same time by making the crud actions real-time for all connected browsers.

So far, it is working except for the real-time part of the comments on posts when I display a specific post (i.e. posts/show).

It probably has to do with the identifiers I used for the turbo_frame_tags and/or the way I broadcast but I can't seem to find the answer. I looked at this question and also a GoRails example of something similar but it still does not work.

My current posts/index

<%= turbo_stream_from "posts" %>

<div class="w-full">
  [...]
  <div class="min-w-full">
    <%= turbo_frame_tag :posts do %>
      <%= render @posts %>
    <% end %>
  </div>
</div>

the partial for each post is as follows:

<%= turbo_frame_tag post do %>
  <div class="bg-white w-full p-4 rounded-md mt-2">
    <h1 class="my-5 text-lg font-bold text-gray-700 text-4xl hover:text-blue-400">
      <%= link_to post.title, post_path(post) %>
    </h1>
   [...]
  </div>
<% end %>

the posts/show replaces the current partial of each post with a more detailed version:

<%= turbo_stream_from @post, :comments %>

<%= turbo_frame_tag dom_id(@post) do %>
  [...]
    <div class="w-full bg-white rounded-md p-4 justify-start mt-1">
      <div class="w-full pt-2">
        <turbo-frame id="total_comments" class="mt-2 text-lg text-2xl mt-2 tex-gray-50">
          <%= @post.comments.size %> comments
        </turbo-frame>
        <%= turbo_frame_tag "#{dom_id(@post)}_comments" do %>
          <%= render @post.comments %>
        <% end %>
        [...]
      </div>
    </div>
  </div>
<% end %>

with the respective models being:

class Post < ApplicationRecord
  validates_presence_of :title
  has_rich_text :content
  has_many :comments, dependent: :destroy
  after_create_commit { broadcast_prepend_to "posts" }
  after_update_commit { broadcast_replace_to "posts" }
  after_destroy_commit { broadcast_remove_to "posts" }
end

and

class Comment < ApplicationRecord
  include ActionView::RecordIdentifier

  belongs_to :post
  has_rich_text :content

  after_create_commit do
    broadcast_append_to [post, :comments], target: "#{dom_id(post)}_comments", partial: 'comments/comment'
  end

  after_destroy_commit do
    broadcast_remove_to self
  end
end

The _comment partial is

<%= turbo_frame_tag dom_id comment do %>
  <div class="container w-full mx-auto bg-white p-4 rounded-md mt-2 border-b shadow">
    <%= comment.content %> - <%= time_tag comment.created_at, "data-local": "time-ago" %>
    [...]
  </div>
<% end %>

as mentioned, real-time on crud for posts work but not for their comments. Any idea what I am doing wrong? Thanks in advance.

Upvotes: 3

Views: 1084

Answers (1)

GGrassiant
GGrassiant

Reputation: 119

I found a solution. It was just a matter of where to put the stream:

in show, <%= turbo_stream_from @post, :comments %> should be after the <%= turbo_frame_tag dom_id(@post) do %>.

And the _comment partial should be like

<%= turbo_stream_from comment %>
<%= turbo_frame_tag dom_id comment do %>
 [...content]
  </div>
<% end %>

There is a more detailed explanation on the hotwired board

Upvotes: 1

Related Questions