完全に理解するTurbo Stream

# 完全に理解するTurbo Streams
## 概要
- turbo-rails gemを使用したturbo_streamの実装方法がわかるようになります。
- Rails6系以前のrails-ujsを使用したAjax機能の代替となるものをTurbo Streamsを使用して作るための説明がメインです。
- broadcastについては説明しません。

## Ajaxの話
Turbo StreamsはRails6系以前のAjaxの実装とイメージが近いのでまずAjaxについて簡単に概要を説明します。
Ajaxは「Asynchronous JavaScript and XML」の略称で、この技術を用いるとページを再読み込みしなくても画面を更新することが出来ます。

Ajaxを理解するにはまず、Webブラウザの基本的な動作について理解しておく必要があります。
ユーザーが何かのリンクをクリックするとブラウザはサーバーに対してリクエストを1つ送信します。
ブラウザは、サーバーから受け取った情報を見て、0からユーザーに見せるための画面を作成します。
0から画面を作成するためサーバーが返す情報はHTMLやCSS・JavaScriptなど画面を作成するために必要なすべての情報が含まれています。

対してAjaxを使用すると、JavaScriptを使って更新したい対象の箇所のみを更新するための情報を要求し、すべての画面ではなく更新したい箇所に必要な画面だけを作成して既存の画面から書き換えることが出来ます。
そのため、サーバーから返す情報量を少なくしたり、画面全体を作成しないので表示速度を早めたりすることが出来ます。

### 通常の画面の更新
デミグラスハンバーグの画像をおろしハンバーグに変えるのに、画面全てを作成するための情報をサーバーに要求します。
![無題のプレゼンテーション (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/ea6e5909-3190-2bfb-1f8b-0e6dda466752.png)


ヘッダーや他の要素を含めたすべての画面を0から作成します。
![orosi_reload.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/51ce9259-9da5-5f75-16e8-54ea86f88d2c.png)
![](orosi_reload.png)

### AjaxやTurboを使用した画面の更新
デミグラスハンバーグの画像をおろしハンバーグに変えるのに、おろしハンバーグ要素のみを作成するための情報をサーバーに要求します。
![demi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/ed0ec030-2fe0-8e76-12ed-d5d30cbb96e7.png)
![](demi.png)

赤枠で囲まれたおろしハンバーグの要素のみを0作成して既存の画面から書き換えます。
![orosi_reload_only.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/85efeacb-3784-734e-4281-692099659eb5.png)
![](orosi_reload_only.png)

## Turbo StreamsをRailsのアプリで実装する

### 前提条件
下記条件はRails7系でrails newを行うと勝手に設定されているかなと思います
- [turbo-rails](https://github.com/hotwired/turbo-rails)gemがインストールされていること(viewでturbo_streamを使いやすくなります。入っていなければインストールしてください)
- `app/javascript/application.js`でturboを使う設定が出来ていること

## 実装方法
### ViewからTURBO_STREAMリクエストを送る

getリクエストを送るリンクの場合下記のように`data: { turbo_stream: true }`をつけるとコントローラーでturbo_streamのリクエストとして受け取ることが出来るようになります。

```erb
<%= link_to 'Edit', edit_post_path(post), data: { turbo_stream: true } %>
```

get以外のリクエストの場合下記のように`data-turbo_method`を指定することでturbo_streamのリクエストを送れます。

```erb
  <%= link_to 'Destroy', post, data: { turbo_confirm: 'Are you sure?', turbo_method: :delete } %>
```

サーバーログに下記のようなログが出ていればOKです。

```
Processing by PostsController#edit as TURBO_STREAM
```

### Controllerの設定
Controllerの設定は特に必要ありません。

ViewからTURBO_STREAMのリクエストを送れていればアクション名と対応する`action名.turbo_stream.erb`ファイルを探してくれます。

```ruby
def edit
  @post = Post.find(params[:id])
  # render :editは書かなくてもedit.turbo_stream.erbファイルを探してくれる
end
```


または、下記のように`render turbo_stream: turbo_streamの構文`でコントローラー側で実装することも出来ます

```ruby
def edit
  @post = Post.find(params[:id])
  render turbo_stream: turbo_stream.replace("post_#{@post.id}", partial: "form", locals: { post: @post })
end
```

### Turbo Streams用のViewを用意する

turbo-railsでは`turbo_stream.アクション 対象要素のID, partial: 表示したい部分テンプレート, locals: 部分テンプレートで使いたい変数`のような形でTurbo Streams用のViewファイルを記述することが出来ます。

```erb
<%= turbo_stream.update "post_#{@post.id}", partial: "form", locals: { post: @post } %>
```

またはパーシャルを指定せずに下記のように直接Erbを記述することが出来ます。

```erb
<%= turbo_stream.update "post_#{@post.id}" do %>
  <%= form_with model: @post do |f| %>
    <%= f.text_field :body %>
    <%= f.submit %>
  <% end %>
<% end %>
```

これは下記のように解釈されブラウザに送られます。
下記の要素がブラウザでキャッチされるとapplication.jsで設定したHotwireが動いてDOMを更新したり、削除したりする操作をしてくれます。

```html
<turbo-stream action="アクション" target="対象要素のID">
  <template>
    パーシャルで指定した内容
  </template>
</turbo-stream>
```

ブラウザのネットワークタブを確認すると上記の形式でレスポンスが返ってきているのを確認できます。
※サンプルコードを簡略化するためHTMLの属性を削っているのでstyleとかが入っています。
![turbo_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/ed4c6967-d102-d5f2-9cf3-ce86287134be.png)
![](turbo_edit.png)

### 複数のturbo_stream
turbo_streamで複数の処理を行いたいときは下記のように一つのViewに複数のturbo_streamの処理を書くことができます。
```erb
<%= turbo_stream.replace "post_form_#{@post.id}", partial: "form", locals: { post: @post } %>

<%= turbo_stream.append "error_messages_#{@post.id}" do %>
  <% @post.errors.full_messages.each do |message| %>
    <%= message %>
  <% end %>
<% end %>
```

## Turbo Streamsの7つのアクション+CSSの指定方法
### Append
対象のidを持つ要素の内側の一番お尻に要素を追加します。

```erb
<%= turbo_stream.append "posts", partial: "post", locals: { post: @post } %>
```

パーシャルを使わない場合こんな感じで書けます。

```erb
<%= turbo_stream.append "posts" do %>
  <div id="<%= dom_id @post %>">
    <%= @post.body %>
    <%= link_to 'Edit', edit_post_path(@post), data: { turbo_stream: true } %>
    <%= link_to 'Destroy', @post, data: { turbo_confirm: 'Are you sure?', turbo_method: :delete } %>
  </div>
<% end %>
```

![15e6fe605ba4f0d50767cfd0c386b038.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/03db0c11-5a2e-7dd3-afb7-5bd2fe45079d.gif)
![](15e6fe605ba4f0d50767cfd0c386b038.gif)

### Prepend
対象のidを持つ要素の内側の先頭に要素を追加します。

```erb
<%= turbo_stream.prepend "posts", partial: "post", locals: { post: @post } %>
```

![prepend.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/6f6660d9-e7b5-52aa-b59e-06ae6f4d153a.gif)
![](prepend.gif)

### Replace
対象のidを持つ要素自体を置き換えます。

```erb
<%= turbo_stream.replace "post_#{@post.id}", partial: "post", locals: { post: @post } %>
```

![replace.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/72359a11-3276-920e-a2b2-a0a2cf3216a2.gif)
![](replace.gif)

### Update
対象のidを持つ要素の中身を書き換えます。

```erb
<%= turbo_stream.update "posts", partial: "post", locals: { post: @post } %>
```

![update.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/70a31120-8c34-a127-741d-09e9b292dac9.gif)
![](update.gif)

### Remove
対象のidを持つ要素を取り除きます。

```erb
<%= turbo_stream.remove "post_#{@post.id}" %>
```

![remove.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/754755b6-9f27-4b5d-33e2-f8df9ecbd37d.gif)
![](remove.gif)
### Before
指定した要素の直前に要素を追加します。

```erb
<%= turbo_stream.before "posts", partial: "form", locals: { post: @post } %>
```

![before.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/de7515d6-d1d1-201d-aac5-3a873658e70d.gif)
![](before.gif)
### After
指定した要素の直後に要素を追加します。

```erb
<%= turbo_stream.after "posts", partial: "form", locals: { post: @post } %>
```

![after.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/4da3da21-9a41-a0a9-1722-bf784fa15004.gif)
![](after.gif)
### class属性を指定する方法
アクション名+`_all`を使うとclass指定することが出来ます。
対象のclassを持つ要素全てを操作します。
classを指定するときはCSSセレクタを使用して`.クラス名`のように書きます。

```erb
<%= turbo_stream.append_all ".content" do %>
  <div>おいちい</div>
<% end %>
```

![turbo_stream_all.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555267/8e88d618-a660-a63f-00bf-0b4d66100021.gif)
![](turbo_stream_all.gif)

## 参照
https://turbo.hotwired.dev/reference/streams
https://github.com/hotwired/turbo-rails/blob/main/app/models/turbo/streams/tag_builder.rb

## 最後に
Turbo Streamsはとっても便利なのでこれを機に使ってみてください。
また、今回の記事を書くにあたって簡単な一枚ページのturbo_streamを使ったアプリを作成したので詳しく見たい方は見てみてください
https://github.com/kenchasonakai/turbo_stream_practice