Ask A Question

Notifications

You’re not receiving notifications from this thread.

Realtime Nested Comments: Part 3 Discussion

Very cool. I imagine a real-time comment feed like this will make a website feel come alive.

One issue I’d love to see addressed in a follow up video is the page kinda moving around as you’re reading comments. I believe Disqus solves this by not I’m immediately appending new comments, but adding a placeholder like “5 new comments” which you can click to reveal.

I imagine we could do something similar with Stimulus but it right require some changes to the broadcasts.

Reply

Are there any performance concerns to keep in mind with having a turbo_stream_from tag for each comment? Does this create a separate connection for each comment? I'm new to websockets/redis, not even quite sure the best way to test this lol

Loving these episodes, and learning so much! Thank you!

Reply

An individual user will create one consumer-connection pair per browser tab, window, or device they have open. ( source )

Reply

Thank you Marc, that answered it!

Reply
Reply

I'm getting the error Devise::MissingWarden in Comments::Comments#create when trying to call the current_user helper in the _comment partial. To check if the current_user is the author of the comment.

Reply

Same problem here. Did you end up finding any solution to this? Right now I am investigating the use of view_components gem but seems heavy handed :/

Reply

I ran into this problem as well, according to DHH this makes sense as partials used for global streaming should be free of global references https://discuss.hotwired.dev/t/authentication-and-devise-with-broadcasts/1752/4?u=shawnaukstak

I got around this by using CSS described here
https://discuss.hotwired.dev/t/authentication-and-devise-with-broadcasts/1752/18?u=shawnaukstak

Reply

How does this work with someones avatar thats uploaded using activestorage? I get the following url for the image

http://example.org/rails/active_storage/representations/redirect/...../chewy.jpeg

where the example.org was set in application_controller_renderer.rb. Even after replacing it to "localhost" it still doesn't work. When you do a full page reload all the avatars show up.

Reply

There's a bug in ActiveStorage. Wrap your call in url_for() to work around it.

# BUG: Uses http://example.com/... when rendering via a model broadcast
# See https://github.com/rails/rails/issues/41795
url_for(obj.avatar.variant(resize: "#{size}x#{size}!"))
Reply

Sean, thanks. I had the same issue and you saved me a ton of time!

Reply

For anyone else that is hitting this issue, you'll want to edit config/environment/development.rb to include:

config.action_controller.default_url_options = { host: "localhost", port: ENV.fetch("PORT", 3000).to_i }

Reply

Great series Chis thanks for putting this together, it was eye opening to how comments can really come alive with Hotwire. A bit of a newbie here, but would it be possible to take it a step further and bring nested comments inline akin to Google Docs as a way to bring more context into the comments themselves?

Reply

Great series! I'm curious how the nesting depth limit that was implemented with the AJAX version can be adapted for this approach? There's quite a bit that's different between the two versions, so I'm not sure where to even begin. Still new to Turbo and Hotwire, so any guidance would be appreciated.

Reply

If anyone is interested, I figured out what I needed to do. For context: in the first half of the Nested Comment Threads in Rails series, Chris introduces two local variables inside the comments/_comment.html.erb partial called nesting and max_nesting. He also creates a helper method called reply_to_comment_id(comment, nesting, max_nesting) that sets the parent_id of the reply comment. I couldn't get this approach to work with Hotwire and Turbo, but thanks to a comment by Jay Killeen on Nested Comment Threads in Rails - Part 3 I was able to get this working:

Create a migration to add nesting to comments:

class AddNestingToComments < ActiveRecord::Migration[6.1]
  def change
    add_column :comments, :nesting, :integer
  end
end

Add this inside the Comment model:

  def set_nesting
    if self.parent.present? && self.parent.nesting.present?
      self.nesting = self.parent.nesting + 1
    else
      self.nesting = 1
    end
  end

  def self.max_nesting
    2
  end

Make this change inside the Commentable concern:

def create
    ...
    ...
    if @parent&.nesting.blank? || @parent&.nesting < Comment.max_nesting
      @comment.parent_id = @parent&.id
      @comment.nesting = @comment.set_nesting
    else
      @comment.parent_id = @parent&.parent_id
      @comment.nesting = @comment.set_nesting
    end
    ...
    ...
end

This may not be the best way to handle things, so if anyone has another approach that is better please feel free to comment.

Reply

Hi Mark, have the exact same problem trying to make "continues thread" working. Will try your approach, but just wanted to see, if you made it work in your production property with above implementation?

Reply

Marc, thank you. This seems to work well for me and saved me hours of time, no doubt.

Reply

Hi Porter/Marc, were you able to implement conditional edit/delete buttons for different user comments?
Currently stuck on that, using following approach => https://www.colby.so/posts/conditional-rendering-with-turbo-stream-broadcasts
But no luck so far...

Reply

Hey cool session! everything worked out nice except when having to use my comments within a namespace.

I fixed it with a custom record helper method like so:

def model_scope_for_path(commentable, comment)
    return [comment] if params[:action] == 'edit'

    if commentable.instance_of?(Session.new.class)
      [:backend, commentable, comment].compact
    else
      [commentable, comment].compact
    end
  end

I think it is a little bit ugly, what do you think? Is there a better way to taggle it? I was also wondering why we have to use 3 different controllers and if this could be even simplified.

Reply

Thanks Chris!

Inside _comment.html.erb I'm calling @item.user as bellow:

<div class="flex flex-col w-full text-gray-700 <%='bg-gray-200 m-1 p-1 rounded-md' if comment.user === @item.user%>">

    <div class="relative flex flex-wrap items-center mb-1">
        <% if comment.user.avatar.attached? %>

        <div class="relative flex flex-col items-center group">
            <%= link_to comment.user do %>
            <%= image_tag url_for(user_avatar(comment.user, 300)), class: "rounded-full h-12 w-12 align-middle border-none shadow-lg mr-2", alt: comment.user.name, label: comment.user.name %>
            <%end%>
            <div class="absolute bottom-0 flex flex-col items-center hidden mb-12 right-1 -left-1 group-hover:flex">
                <span class="relative z-10 p-2 text-xs font-semibold leading-none leading-relaxed text-white whitespace-no-wrap bg-black rounded-md shadow-lg min-w-max"><%= comment.user.name%></span>
                <div class="w-3 h-3 -mt-2 rotate-45 bg-black"></div>
            </div>
        </div>
      </div>
   </div>

And I get an error undefined methoduser' for nil:NilClassfrom@item.user.
How could I get
@item` variable works inside comment?
Any better approach?
Thanks!

Reply

what is your item? do you want the current_user?

Reply

Something else I was wondering if the format.html redirect @commentable lines are necessary. I deleted them all, because they had an error in my case and it didn't anything.

Reply

it was really painful to add the current_user (if you want to have edit and delete only for the person who created that comment) to this since device's current_user method it not working with hotwire. I had to add a lot of passing of the current_user to make it work.

Reply

In scenarios, like this, I use the user method on the object. Since the comment.user will return the correct user for that comment.

Reply

But isn't the problem that, when comparing comment.user to current_user and current_user is not available through the renderer??

Interested to know what OuttaSpaceTime was doing to successfully pass the current_user around. Currently struggling to access it via the comment.rb callbacks like after_create_commit.

Reply

Did you pass the current_user to the partial?

<%= render comment, current_user: current_user %>

Reply

Hey, I'm fighting with this as well and I still don't understand how it's possible at all to make edit & delete buttons visible in the broadcasted partial depending on the user. I don't see how this can work.

The commit itself and the after_create_commit are running in the context of the creating user, i.e. Current.user is always the user creating the post, which means everyone receives the same partial!

Only ways that I see is

  • Use JS / Stimulus to postprocess the partial in the browser.
  • Not sending the partial, but a trigger, so that every client pulls the update in his own context

Can anyone correct me, please?

Reply

Depth-limited nested comments based on this author's video.
https://github.com/secretpray/Hotwire-CRUD-MODAL

Reply

Unfortunately, with Rails 7, the forms with action-text text-areas are having the issue outlined here: https://github.com/hotwired/turbo-rails/issues/243

Reply

Hi. Yes, I'm experiencing that, at the moment I am applying the solution proposed by 'joostvanrijn'. Basically checking if the session exists before rendering the partials like:
<% if session.enabled? %>
But I still have an issue with brand new comments, toggle action does not work if I do not refresh.

Reply

Is it possible to render signed_in? inside partial _comment_with_replies.html.erb without errors?

Inside the partial, I have <% if signed_in? %> to check if user signed in first to show Reply section. When i do so, it throw Devise famous error Devise could not find theWarden::Proxyinstance on your request environment..

Any solution?

Reply

<%= render comment_with_replies, current_user: current_user %>

if current_user.present?

Reply

Thanks a lot for this tutorial ! :)

How would you deal with N+1 ? Using :includes doesn't look quite effective (because of polymorphic associations ?).

Should we fine tune ActiveRecord::Associations::Preloader in that case ?

Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 85,972+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.