ruby-on-railsdeviseruby-on-rails-7hotwire-railsturbo-rails

Can't pass current_user into rails 7 turbo stream


I am trying to learn turbo with rails and have come to a problem that I can't seem to figure out. I have a structure where every Applicant has Messages. Two users can send messages to each other through an Applicant. This stream works now.

However, I have tried to limit the delete to only the user that posted the message. This works when the messages are initially loaded, or the page is refreshed. But when the messages are streamed, the delete option does not appear.

It does not seem to recognise what current_user (devise) is on the stream. I have tried adding current_user as a local in the controller and view, but that doesn't work. I can't see a way to add current_user in the model.

One of the issues, I would imagine, is that I feel like my create method for messages and the broadcast after save method are doing the same thing.

Can anybody identify where I can pass the current_user into the turbo stream so the delete button appears? Failing that, any issues with my methodology would also help me in tracking the issue down

Here is the show action in the Applicant controller (what runs when the page initially loads):

class ApplicantController < ApplicationController
  before_action :authenticate_user!
  def show
    @applicant = Applicant.find_by_hashid(params[:hashid])
    
    max_messages = 200
    @messages = @applicant.messages.order(created_at: :asc).last(max_messages)
    
    current_user == @applicant.project.user ? @is_owner = true : @is_owner = false

    @message = Message.new

    respond_to do |format|
      format.html
      format.turbo_stream
    end
  end
end

Here is the create action in the controller for messages (what gets run when a new message is created):

class MessageController < ApplicationController
  before_action :authenticate_user!
  def create
    @message = Message.new(message_params)
    respond_to do |format|
      if @message.save
        format.turbo_stream { render turbo_stream: turbo_stream.append("applicant_messages_#{@message.applicant_id}", partial: "messages/message", locals: { message: @message }), notice: 'Message was successfully created.' }
        format.json { render :show, status: :created, message: @message }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @message.errors, status: :unprocessable_entity }
      end
    end
  end
end

Here is the model for messages:

class Message < ApplicationRecord
  after_save_commit -> { broadcast_append_later_to "applicant_messages_#{self.applicant_id}", partial: "messages/message", locals: { message: self }, target: "messages" }
  after_destroy_commit -> { broadcast_remove_to "applicant_messages_#{self.applicant_id}", target: "message_#{self.id}" }
end

Here is the show.html.erb where the messages appear:

<div class="container">
  <div>
    <%= turbo_stream_from "applicant_messages_#{@applicant.id}" %>
    <div id="messages">
      <%= render @messages %>
    </div>
  </div>
</div>

<div class="container">
  <%= form_for @message, url: message_create_path(@applicant.hashid),data: { controller: "message ", action: "turbo:submit-end->message#reset"}, method: :post, :html => {:id => "message_form"}, remote: true do |f| %>
  <%= f.hidden_field :is_owner, :value => @is_owner %>
  <%= f.hidden_field :applicant_id, :value => @applicant.id %>
  <%= f.text_field :body, autofocus: true, autocomplete: 'off', :placeholder=>"Enter message", :id=>"message_body", class: "form-control", data: { turbo_stream: true } %>
  <%= f.submit 'Post message', class: 'btn btn-primary d-inline', data: {disable_with: "Working on it..."}  %>
</div>

Here is the _messages.html.erb partial:

<%= turbo_frame_tag dom_id(message) do %>
  <p>
    <%= message.is_owner == true ? "#{message.applicant.project.user.first_name}" : "#{message.applicant.user.first_name}" %> (<%= message.id %>): 
    <%= message.body %>
    <% if message.is_owner == @is_owner %>
      [<%= link_to "delete", message_delete_path(message.hashid), method: :delete, data: {turbo_method: :delete} %>]
    <% end %>
  </p>
<% end %>

Solution

  • I suggest to pass user as local variable to the partial, and check it inside

    Also for broadcasting suggest use only controllers. It's some kind of anti-pattern that models can send something through network, and it is especially bad idea to do it using callbacks

    It is just idea, you can improve it and apply to your code

    In create action of messages controller:

    if @message.save
      # or use turbo_stream here
      @message.broadcast_append_to(
        "applicant_messages_#{@message.applicant_id}",
        partial: "messages/message",
        target: "messages",
        locals: { message: @message, user: current_user }
      )
    end
    

    In app/views/messages/_message.html.erb partial:

    <% if message.user_id == user.id %>
      <%= render something.that.can.see.only.message.owner %>
    <% else %>
      <%= render something.that.see.others %>
    <% end %>
    

    Of course you need in this case pass current_user as local user in the message container in the app/views/applicants/show.html.erb

    <div id="messages">
      <%= render @messages, user: current_user %>
    </div>