Note that Action Cable is deprecated in favor of hotwire. Here is old example

Example of local hotwire folders ~/rails/tmp/turbo_modal_boostrap ** ~/rails/tmp/hotwire ** ~/rails/tmp/rails_forms ~/rails/nested-habtm-forms-for-associations-in-rails

GET are HTML and PATCH/POST are TURBO_STREAM requests for ALL forms on the page. You should be able to edit inline twice double (at this stage, without importing turbo in js… if you import than add turbo_stream.replace response) There is an error Error: Form responses must redirect to another location if we submit a form without turbo_stream_tag

To run javascript on when turbo frame is loaded you can listen to events or you can use stimulus controller connect event.

Note that you do not have access to request when we broadcast over websocket so to differentiate between current_user you need to use data attributes or meta tag with and add class in js if matches

Another example is to automatically open modal, and close on submit

<%= turbo_frame_tag 'modal' do %>
  <div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" data-controller='start-modal-on-connect'>
    <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">&times;</span></button>
    <%= button_to 'Ok', interests_path(to_member_profile_id:, class: 'btn btn-primary mb-3', 'data-action': 'start-modal-on-connect#close' %>

// app/javascript/controllers/start_modal_on_connect_controller.js
import { Controller } from 'stimulus'

export default class extends Controller {
  connect() {

  close() {

Usually when modal is rendered from server, I use target value to target "modal-123" and inside modal I use target: "express_interest_buttons-123"

# app/views/interets/index.html.erb
            <%= turbo_frame_tag "modal-#{}", target: "express_interest_buttons-#{}" %>
            <%= render 'interests/express_interest_buttons', member_profile: member_profile %>
              which is:
              <%# turbo_frame_tag "express_interest_buttons-#{}", target: "modal-#{}" do %>
                <%= link_to new_cancel_interest_path(interest), class: "btn btn-primary interest-shown interestbtn" do %>

# app/controllers/interests_controller.rb
  def new_cancel
    authorize @interest

  def cancel
    authorize @interest
    render partial: 'express_interest_buttons', locals: { member_profile: @interest.to_member_profile }

# app/views/interes/new_cancel.html.erb
<%= turbo_frame_tag "modal-#{}", target: "express_interest_buttons-#{}" do %>
  <%= button_to 'Ok', cancel_interest_path(@interest), class: 'btn btn-primary mb-3', 'data-action': 'start-modal-on-connect#close', method: :patch %>

// app/views/layouts/application.html.erb
    <meta content="Free Matrimonial website for Indian Community in US & Canada" name="description" />

// app/javascript/controllers/message_chat_controller.js
import { Controller } from 'stimulus'

export default class extends Controller {
  // <li class="send-msg" data-controller='single-message' data-single-message-member-profile-id='<%= %>' hidden>
  connect() {
    if (this.currentMemberProfileId == this.memberProfileId)

    this.element.hidden = false

  get currentMemberProfileId() {
    return document.querySelector("[name=current-member-profile-id]").content

  get memberProfileId() {

Redirect inside turbo_frame_tag Solution is using stimulus event

// app/javascript/controllers/turbo_form_submit_redirect_controller.js
import { Controller } from "stimulus"
import * as Turbo from "@hotwired/turbo"

export default class extends Controller {
  connect() {
    this.element.addEventListener("turbo:submit-end", (event) => {

  next(event) {
    if (event.detail.success) {

Usage is with

<turbo-frame id='phone' data-controller='turbo-form-submit-redirect'>

note that it needs to be on first frame in case you have steps that replaces several templates

Error when adding stimulus controller that use importing in js like import * as Turbo from "@hotwired/turbo"

Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "turbo-frame" has already been used with this registry

and I see that cable connection is not created when this error is present. Solution is to import in main pack anywhere (below or above import "controllers"). Error will remain, but at least cable connection is created

// without this line, cable websocket connection will fail if you use turbo in
// stimulus controllers: import * as Turbo from "@hotwired/turbo"
import '@hotwired/turbo-rails'

Note that when you include turbo in js than default html response has double request problem (search above for double)

WS websocket tab in Network tab is showing Websocket connection when you are running bin/webpack-dev-server server which is sending updates using that connnection.

Turbo drive

Once installed it is enabled for all links. To disable for particular link you can use

<a href="/" data-turbo="false">Disabled</a>

Group disable does not work in Firefox (probably since bubling is not the same) so better is to disable each link

Devise fix

# app/controllers/turbo_controller.rb
class TurboController < ApplicationController # rubocop:todo Lint/ConstantDefinitionInBlock
  class Responder < ActionController::Responder
    def to_turbo_stream
      controller.render(options.merge(formats: :html))
    rescue ActionView::MissingTemplate => e
      raise e if get?

      if has_errors? && default_action
        render rendering_options.merge(formats: :html, status: :unprocessable_entity)
        redirect_to navigation_location

  self.responder = Responder
  respond_to :html, :turbo_stream

# config/initializers/devise.rb
Rails.application.reloader.to_prepare do
  class TurboFailureApp < Devise::FailureApp # rubocop:todo Lint/ConstantDefinitionInBlock
    def respond
      if request_format == :turbo_stream

    def skip_format?
      %w[html turbo_stream */*].include? request_format.to_s

Devise.setup do |config|

  # ==> Controller configuration
  # Configure the parent class to the devise controllers.
  config.parent_controller = 'TurboController'

  # ==> Warden configuration
  config.warden do |manager|
    manager.failure_app = TurboFailureApp

Sample apps

bootstrap modal twitter todoapp todo