Excerpt from The Turbo Rails Tutorial
Tags: rails, hotwire
Published: 2023-09-29 19:35:10

I finished [The Turbo Rails Tutorial](https://www.hotrails.dev/turbo-rails). 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.
```ruby
# Gemfile
gem "turbo-rails"
```
```js
// 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"`.
```erb
<%= 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.
```js
// app/javascript/application.js

import { Turbo } from "@hotwired/turbo-rails"
Turbo.session.drive = false
```
```erb
<%# 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"`.
```erb
<%# 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.
```erb
<%# Source Page %>

<%= turbo_frame_tag "tag_name" do %>
  Old content
<% end %>
<%# equal to <turbo-frame id="tag_name"></turbo-frame> %>
```
```erb
<%# 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.
```erb
<%# 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 %>
```
```erb
<%# 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.
```erb
<%# 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`.
```ruby
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.
```erb
<%= 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.
```ruby
# 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.
```ruby
# 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.
```erb
<%# 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.
```erb
<%= 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
```yml
# 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. 
```ruby
# app/models/quote.rb

class Quote < ApplicationRecord
  after_create_commit -> { broadcast_prepend_to "quotes", partial: "path/to/file", locals: { quote: self }, target: "quotes" }
end
```
```html
<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
```erb
<%= 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
```ruby
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
```ruby
# Gemfile

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

<%= turbo_stream_from current_company, "quotes" %>
```
## Chapter 7
```js
// app/javascript/controllers/index.js

import { application } from "./application"

import RemovalsController from "./removals_controller.js"
application.register("removals", RemovalsController)
```
```js
// app/javascript/controllers/removals_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  remove() {
    this.element.remove()
  }
}
```
```erb
<%# 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 %>
```
```ruby
# 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
```
```erb
%# app/views/quotes/create.turbo_stream.erb %>

<%= turbo_stream.prepend "flash", partial: "layouts/flash" %>
```
```erb
<div id="flash" class="flash">
  <%= render "layouts/flash" %>
</div>
```
## Chapter 9
```erb
<%= button_to "Delete",
              quote_line_item_date_path(quote, line_item_date),
              method: :delete,
              form: { data: { turbo_confirm: "Are you sure?" } } %>
```
```html
<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>
```