Search code examples
ruby-on-railsrubyruby-on-rails-7stimulusjshotwire-rails

Turbo Streams in Rails 7


I have been following this tutorial https://www.youtube.com/watch?v=TKIbtbYyRdE&list=PL6SEI86zExmvb4qjVHJWrKRQrKjWksQ81&index=34 followed by its 2nd part https://www.youtube.com/watch?v=akrXCho2m6Y&list=PL6SEI86zExmvb4qjVHJWrKRQrKjWksQ81&index=34

I am so Fed up trying to find whats wrong,

<%= link_to "Connect", connections_path, class:"btn btn-primary", data: { controller:"connections", turbo_method: :post, requester_id: current_user.id, connected_id: user.id, connections_target:"connection" } %>

this above link_to uses StimulusJS controller "connections" which should do a POST request

connections_controller.js

import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="connections"
export default class extends Controller {
 static targets = ["connection"];

 connect() {}

 initialize() {
 this.element.setAttribute("data-action", "click->prepareConnectionParams");
  }

 prepareConnectionParams(event) {
    event.preventDefault();

 this.url = this.element.getAttribute("href");

 const element = this.connectionTarget;
 const requesterId = element.dataset.requesterId;
 const connectedId = element.dataset.connectedId;

 const connectionBody = new FormData();

    connectionBody.append("connection[user_id]", requesterId);
    connectionBody.append("connection[connected_user_id]", connectedId);
    connectionBody.append("connection[status]", "pending");

    console.log("Connection Body:", connectionBody);

 fetch(this.url, {
      method: "POST",
      headers: {
        Accept: "text/vnd.turbo-stream.html",
 "X-CSRF-Token": document
          .querySelector('meta[name="csrf-token"]')
          .getAttribute("content"),
      },

      body: connectionBody,
    })
      .then((response) => response.text())
      .then((html) => Turbo.renderStreamMessage(html));
  }
}

after that i have ConnectionsController.rb

class ConnectionsController < ApplicationController 
  before_action :authenicate_user!
 
 def create
 Rails.logger.debug "Params received: #{params.inspect}"

    @connection = current_user.connections.new(connection_params)
    @connector = User.find(connection_params[:connected_user_id])

    respond_to do |format|
 if @connection.save
        format.turbo_stream { 
          render turbo_stream:
          turbo_stream.replace(
 "user-connection-status",
 partial: "connection/create",
 locals: { connector: @connector }
          )
        }
 end
 end
 end

 private
 def connection_params
    params.require(:connection).permit(:user_id, :connected_user_id, :status)
 end
end

When i click the link, it shows Error 400 Bad Request for POST in console and refreshes the page. it was not working so i used Rails.logger.debug "Params received: #{params.inspect}" so in terminal logs it is saying that params is not being sent Params received: #<ActionController::Parameters {"controller"=>"connections", "action"=>"create"} permitted: false>


Solution

  • You're missing controller for your action:

    this.element.setAttribute("data-action", "click->connections#prepareConnectionParams");
    //                                               ^^^^^^^^^^^
    

    Here is another example with Stimulus action params:
    https://stimulus.hotwired.dev/reference/actions#action-parameters

    # there is no need for `turbo_method: :post` since we're doing our own posting
    # `connections_target` is also a bit redundant on a controller element itself
    <%= link_to "Connect", connections_path,
      data: {
        controller: "connections",
        connections_requester_id_param: 1,
        connections_connected_id_param: 2,
        connections_status_param: "pending",
      }
    %>
    
    // app/javascript/controllers/connections_controller.js
    
    import { Controller } from "@hotwired/stimulus";
    
    export default class extends Controller {
      connect() {
        this.element.setAttribute("data-action", "connections#prepareConnectionParams");
      }
    
      prepareConnectionParams(event) {
        event.preventDefault();
        const { params } = event;
        const body = new FormData();
    
        body.append("connection[user_id]", params.requesterId);
        body.append("connection[connected_user_id]", params.connectedId);
        body.append("connection[status]", params.status);
    
        fetch(this.element.getAttribute("href"), {
          method: "POST",
          headers: {
            Accept: "text/vnd.turbo-stream.html",
            "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
          },
          body,
        })
          .then((response) => response.text())
          .then((html) => Turbo.renderStreamMessage(html));
      }
    }
    

    I have not looked at the tutorial, just make sure all that ^ leads to something useful, because you can accomplish the same thing with a regular form and much less code:

    <%= button_to "Connect", connections_path, 
      params: {
        connection: {
          user_id: 1,
          connected_user_id: 2,
          status: "pending",
        }
      }
    %>
    

    This will send a TURBO_STREAM POST request with the same params and will handle turbo_stream response for you.