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"
// app/javascript/application.js
import "@hotwired/turbo-rails"
import "./controllers"
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> %>
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
<%# app/views/layouts/application.html.erb %>
<body data-turbo="false">
...
</body>
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">%>
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> %>
<%# Target Page %>
<%= turbo_frame_tag "tag_name" do %>
New content
<% end %>
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 %>
<%# Target page %>
<%= turbo_frame_tag "change_tag_name" do %>
New content
<% end %>
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> %>
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"
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 %>
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
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
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, "" %>
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 %>
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
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
<turbo-stream action="prepend" target="quotes">
<template>
<turbo-frame id="quote_123">
<!-- The HTML for the quote partial -->
</turbo-frame>
</template>
</turbo-stream>
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> %>
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
Chapter 6
# Gemfile
gem "devise", "~> 4.8.1"
bundle install
bin/rails generate devise:install
bin/rails generate devise User
bin/rails db:migrate
class Quote < ApplicationRecord
broadcasts_to ->(quote) { [quote.company, "quotes"] }, inserts_by: :prepend
end
<%# app/views/quotes/index.html.erb %>
<%= turbo_stream_from current_company, "quotes" %>
Chapter 7
// app/javascript/controllers/index.js
import { application } from "./application"
import RemovalsController from "./removals_controller.js"
application.register("removals", RemovalsController)
// app/javascript/controllers/removals_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
remove() {
this.element.remove()
}
}
<%# 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 %>
# 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
%# app/views/quotes/create.turbo_stream.erb %>
<%= turbo_stream.prepend "flash", partial: "layouts/flash" %>
<div id="flash" class="flash">
<%= render "layouts/flash" %>
</div>
Chapter 9
<%= button_to "Delete",
quote_line_item_date_path(quote, line_item_date),
method: :delete,
form: { data: { turbo_confirm: "Are you sure?" } } %>
<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>
Top comments (1)
That
Turbo.session.drive = false
bit is useful if you're only using Turbo in select places for Frames/Streams and do not want it to affect navigation elsewhere.