Turbo stream redirect with current user in partial

I have a list of message partials that I render on an index/list page. The message partials make use of the current_user to see if an edit button should be displayed. Then I have a separate page where the user can create a new message. In the create success case, I just have a redirect, like so:

  def create
    @message = current_user.messages.build(message_params)
    respond_to do |format|
      if @message.save
        format.html { redirect_to messages_url, status: 303, notice: "Message successfully created" }
      else
        format.turbo_stream do
          render turbo_stream: turbo_stream.replace(@message, partial: "messages/form", locals: { message: @message })
        end
      end
    end
  end

I understand that a turbo stream doesn’t have access to the global state, so I need to pass the user into the partial as a local. But, when I’m redirecting, there’s no way to set a user local. So, when I submit a valid message, the request hangs with a pending status for awhile and then errors out when it can’t find the user. If I comment out the piece that uses the user, the interaction works as expected. But, I need the user to figure if I should display the edit link. How do I use Turbo in this situation?

Since you are calling a turbo stream from within your controller, you can just pass your partial your current user object.

render turbo_stream: turbo_stream.replace(@message, partial: "messages/form", locals: { message: @message, current_user: current_user })

Hi, @jclarke. Thank you for responding. The problem isn’t the error case, it’s the success case. The messages/form partial doesn’t make use of the current_user, so I don’t need the user in this context. It’s when I redirect to the messages_url after a successful save of a message, which renders the messages/index.html.erb view. This view in turn renders messages/_message.html.erb, which makes use of the current_user. So the question is, how do I incorporate the current user into a turbo stream redirect?

format.html { redirect_to messages_url, status: 303, notice: "Message successfully created" }

I’m not sure this is a Turbo problem. I mean, there isn’t really any such thing as a “Turbo redirect”. They’re the same as any other redirect, Turbo just skips the full-page reload.

If you didn’t have Turbo, presumably you’d have the same issue? It sounds like the problem is messages#index.

I’m not sure how the mechanics of your current_user method work, but three ideas come to mind:

  1. Pass current user as a local in your messages#index action (and then maybe also down from messages/index into the messages/_message partial)
  2. Define current_user as a helper_method, probably in ApplicationController
  3. Add a Current class with a user attribute. This has global access. You’d just do Current.user rather than current_user.

I’d go with option 3 personally, but the choice is yours.

If you went with either options 2 or 3, you could skip passing the current_user in your render turbo_stream call too.

Hi, @dan. Thank you for chiming in! If I remove Turbo from the situation and use “standard” Rails the system works without issue. I basically have #1 and #2 already. I’m using Devise for authentication, so the current_user method is available in both the views and controllers. And, in my messages/index.html.erb file, I’m passing in the user as a local to messages/_message.html.erb.

But, #3… yeah. Maybe the idea of a current user is really a special case and I need a global/singleton of it. This always feels a little unclean to me, but maybe I’m just cargo culting “globals are bad” and this is a case where it makes sense. I mean, DHH recommends it, and there’s framework support for it, so…

Okay, thank you. I think #3 is probably the direction I need to go.

Hmmm interesting…I’m pretty intrigued how Turbo is breaking this…I don’t use Devise so am not too sure, but it sure doesn’t feel like this should break

Are you using Turbo broadcasts? Because the issue could be there…

No, I haven’t layered broadcasting in yet. What I have is pretty similar to what the standard Rails scaffold would give you, with the concept of a current user thrown in. Before your comment about the Current.user global, I was wondering if Turbo wasn’t really intended to be used holistically. Like maybe there are a number of cases where you’re meant to turn it off. But, I’m struggling to find another case like current user where this would come up. It’s also probably a problem unique to Devise and its use of Warden under the hood. Doing auth in a piece of Rack middleware may not play nicely with Turbo.

Just as a followup, I added in the Current.user global using ActiveSupport::CurrentAttributes approach. This worked!

2 Likes

here is a youtube video on using devise with Turbo. I am gladd the Current.user method works. I will probably that myself.

Hi Elliot ! Can you detail a bit what you did ? I’m having trouble inserting the tails authenticate method in the description they give about the CurrentAttribute…
thanks

@elliotlarson

Let’s assume you have this partial view, and you want to have an edit link to be displayed on the message created by current user, you can include if else statement in your view.

_message.html.erb

<%= content_tag :div, id: dom_id(message) do %>

<%= message.content %>
<% if @current_user  == message.user %>
<%= link_to "edit", edit_message_path(message) %>
<% end %>

<% end %>

Then in your controller, you can do this

def create
    @message = current_user.messages.build(message_params)
    respond_to do |format|
      if @message.save
        format.turbo_stream do
           render turbo_stream: 
                turbo_stream.replace( 
                dom_id(@message), // add this line
                partial: "messages/message",
                locals: { message: @message, current_user: current_user }
        )
    end
        format.html { redirect_to messages_url }
      else
        format.turbo_stream do
          render turbo_stream: turbo_stream.replace(@message, partial: "messages/form", locals: { message: @message })
        end
      end
    end
  end

In order for dom_id(@message) to work correctly in the controller, kindly add include ActionView::RecordIdentifier in your controller, and the edit link should be displayed when you submit the message. I’m not sure what’s your view looks like, but I hope this helps you.