Excerpt from The Turbo Rails Tutorial

obrkn

Ken Obara

Posted on September 29, 2023

Excerpt from The Turbo Rails Tutorial

I finished The Turbo Rails Tutorial. However, it's too long to look back. So, I picked up the useful codes from that.

Chapter 3

We can set up Turbo Drive as follows.

# Gemfile
gem "turbo-rails"
Enter fullscreen mode Exit fullscreen mode
// app/javascript/application.js
import "@hotwired/turbo-rails"
import "./controllers"
Enter fullscreen mode Exit fullscreen mode

By default, Turbo Drive converts all link clicks and form submissions into AJAX requests with event.preventDefault().

To disable Turbo Drive on a link or a form, we can use data-turbo="false".

<%= link_to "Link", path, data: { turbo: false } %>
<% # equal to <a data-turbo="false">Link</a> %>
Enter fullscreen mode Exit fullscreen mode

We can disable Turbo Drive on the whole application as follows.

// app/javascript/application.js

import { Turbo } from "@hotwired/turbo-rails"
Turbo.session.drive = false
Enter fullscreen mode Exit fullscreen mode
<%# app/views/layouts/application.html.erb %>

<body data-turbo="false">
  ...
</body>
Enter fullscreen mode Exit fullscreen mode

To reload the whole page if there are differences in the <head>, we can use data-turbo-track="reload".

<%# app/views/layouts/application.html.erb %>

<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%# equal to <link rel="stylesheet" href="..." data-turbo-track="reload">%>
Enter fullscreen mode Exit fullscreen mode

Chapter 4

turo_frame_tag

Rule1
When clicking on a link within a Turbo Frame, if there is a frame with the same id on the target page, Turbo will replace the content of the Turbo Frame of the source page with the content of the Turbo Frame of the target page.

<%# Source Page %>

<%= turbo_frame_tag "tag_name" do %>
  Old content
<% end %>
<%# equal to <turbo-frame id="tag_name"></turbo-frame> %>
Enter fullscreen mode Exit fullscreen mode
<%# Target Page %>

<%= turbo_frame_tag "tag_name" do %>
  New content
<% end %>
Enter fullscreen mode Exit fullscreen mode

Rule3
A link can target a Turbo Frame it is not directly nested in, thanks to the data-turbo-frame data attribute. In that case, the Turbo Frame with the same id as the data-turbo-frame data attribute on the source page will be replaced by the Turbo Frame of the same id as the data-turbo-frame data attribute on the target page.

<%# Source page %>

<%= turbo_frame_tag "steady_tag_name" do %>
  <%= link_to "Link", path, data: { turbo_frame: "change_tag_name" } %>
<% end %>

<%= turbo_frame_tag "change_tag_name" do %>
  Old content
<% end %>
Enter fullscreen mode Exit fullscreen mode
<%# Target page %>
<%= turbo_frame_tag "change_tag_name" do %>
  New content
<% end %>
Enter fullscreen mode Exit fullscreen mode

Note
When using the "_top" keyword, the whole page content changes to the target page.

<%# Source page %>

<%= link_to "Link", path, data: { turbo_frame: "_top" } %>
<%# equal to <a data-turbo-frame="_top">Link</a> %>
Enter fullscreen mode Exit fullscreen mode

Expression
There is the useful method dom_id.

dom_id(@quote) # => "quote_1"
dom_id(Quote.new) # => "new_quote"
dom_id(Quote.new, "prefix") # "prefix_new_quote"
Enter fullscreen mode Exit fullscreen mode

The following blocks of code are equivalent.

<%= turbo_frame_tag "quote_#{@quote.id}" do %><% end %>
<%= turbo_frame_tag dom_id(@quote) do %><% end %>
<%= turbo_frame_tag @quote do %><% end %>
Enter fullscreen mode Exit fullscreen mode

turbo_stream

There are several methods for turbo_stream.

# Remove a Turbo Frame
turbo_stream.remove

# Insert a Turbo Frame at the beginning/end of a list
turbo_stream.append
turbo_stream.prepend

# Insert a Turbo Frame before/after another Turbo Frame
turbo_stream.before
turbo_stream.after

# Replace or update the content of a Turbo Frame
turbo_stream.update
turbo_stream.replace
Enter fullscreen mode Exit fullscreen mode

This is a controller example.

# app/controllers/quote_controller.rb

def action
  # some process

  # success
  respond_to do |format|
    format.html { redirect_to quote_path, notice: "successfully done." }
    format.turbo_stream
  end

  # failed
  render :action, status: :unprocessable_entity
end
Enter fullscreen mode Exit fullscreen mode

This is an erb file example.

<%# app/views/quote/action.turbo_stream.erb %>

<%= turbo_stream.remove @quote %>
<%# equal to <turbo-stream action="remove" target="quote_908005780"></turbo-stream> %>

<%= turbo_stream.prepend "quote" do %>
  New content
<% end %>
<%= turbo_stream.update Quote.new, "" %>
Enter fullscreen mode Exit fullscreen mode

This is a target file example.

<%= turbo_frame_tag @quote do %>
  Some content
<% end %>

<%= turbo_frame_tag Quote.new %>
<%= turbo_frame_tag "quote" do %>
  Some content
<% end %>
Enter fullscreen mode Exit fullscreen mode

Chapter 5

We can real-time updates with Turbo Streams and Action Cable

settings

# config/cable.yml

development:
  adapter: redis
  url: redis://localhost:6379/1
Enter fullscreen mode Exit fullscreen mode

We can write the next code and when a new record is created, the HTML is delivered via WebSocket.

# app/models/quote.rb

class Quote < ApplicationRecord
  after_create_commit -> { broadcast_prepend_to "quotes", partial: "path/to/file", locals: { quote: self }, target: "quotes" }
end
Enter fullscreen mode Exit fullscreen mode
<turbo-stream action="prepend" target="quotes">
  <template>
    <turbo-frame id="quote_123">
      <!-- The HTML for the quote partial -->
    </turbo-frame>
  </template>
</turbo-stream>
Enter fullscreen mode Exit fullscreen mode

Target page

<%= turbo_stream_from "quotes" %>
<%# equal to <turbo-cable-stream-source
  channel="Turbo::StreamsChannel"
  signed-stream-name="very-long-string"
>
</turbo-cable-stream-source> %>
Enter fullscreen mode Exit fullscreen mode

Any other broadcast methods

after_create_commit -> { broadcast_prepend_to "quotes" }
after_update_commit -> { broadcast_replace_to "quotes" }
after_destroy_commit -> { broadcast_remove_to "quotes" }

after_create_commit -> { broadcast_prepend_later_to "quotes" }
after_update_commit -> { broadcast_replace_later_to "quotes" }

broadcasts_to ->(quote) { "quotes" }, inserts_by: :prepend
Enter fullscreen mode Exit fullscreen mode

Chapter 6

# Gemfile

gem "devise", "~> 4.8.1"
Enter fullscreen mode Exit fullscreen mode
bundle install
bin/rails generate devise:install
bin/rails generate devise User
bin/rails db:migrate
Enter fullscreen mode Exit fullscreen mode
class Quote < ApplicationRecord
  broadcasts_to ->(quote) { [quote.company, "quotes"] }, inserts_by: :prepend
end
Enter fullscreen mode Exit fullscreen mode
<%# app/views/quotes/index.html.erb %>

<%= turbo_stream_from current_company, "quotes" %>
Enter fullscreen mode Exit fullscreen mode

Chapter 7

// app/javascript/controllers/index.js

import { application } from "./application"

import RemovalsController from "./removals_controller.js"
application.register("removals", RemovalsController)
Enter fullscreen mode Exit fullscreen mode
// app/javascript/controllers/removals_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  remove() {
    this.element.remove()
  }
}
Enter fullscreen mode Exit fullscreen mode
<%# app/views/layouts/_flash.html.erb %>

<% flash.each do |flash_type, message| %>
  <div
    class="flash__message"
    data-controller="removals"
    data-action="animationend->removals#remove"
  >
    <%= message %>
  </div>
<% end %>
Enter fullscreen mode Exit fullscreen mode
# app/controllers/quotes_controller.rb

def create
  @quote = current_company.quotes.build(quote_params)

  if @quote.save
    respond_to do |format|
      format.html { redirect_to quotes_path, notice: "Quote was successfully created." }
      format.turbo_stream { flash.now[:notice] = "Quote was successfully created." }
    end
  else
    render :new
  end
end
Enter fullscreen mode Exit fullscreen mode
%# app/views/quotes/create.turbo_stream.erb %>

<%= turbo_stream.prepend "flash", partial: "layouts/flash" %>
Enter fullscreen mode Exit fullscreen mode
<div id="flash" class="flash">
  <%= render "layouts/flash" %>
</div>
Enter fullscreen mode Exit fullscreen mode

Chapter 9

<%= button_to "Delete",
              quote_line_item_date_path(quote, line_item_date),
              method: :delete,
              form: { data: { turbo_confirm: "Are you sure?" } } %>
Enter fullscreen mode Exit fullscreen mode
<form data-turbo-confirm="Are you sure?" class="button_to" method="post" action="/quotes/123/line_item_dates/456">
  <input type="hidden" name="_method" value="delete" autocomplete="off">
  <button class="btn btn--light" type="submit">Delete</button>
  <input type="hidden" name="authenticity_token" value="long_token" autocomplete="off">
</form>
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
obrkn
Ken Obara

Posted on September 29, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related