Rails7で更新成功時にBootstrapのToastsを表示する

Rails で bootstrap を使うことになったので、せっかくだから更新成功時に Toasts を表示しよう考えました。

Bootstrap の Toasts は自分で初期化する必要があるのですが、window の load イベントを契機に初期化しようとして失敗しました。
というのも Rails には Turbolinks という機能があって、History API を使って遷移を行うために、ページ遷移時に window の load イベントが発火しないのです。

Toasts の初期化の仕方や show の呼び方をいろいろ試した結果、とりあえず表示するところまではたどり着けたので、備忘録代わりに記録を残します(もっと上手いやり方があったら知りたい)。

Docker で環境を作っているので、そこらへんは適宜読み替えてください。

# 環境

+ Ruby 3.1.1
+ Ruby on Rails 7.0.2
+ Docker 20.10.10, build b485636
+ Docker Compose v2.2.3
+ node.js v16.14.0
+ bootstrap 5

# インストール

https://qiita.com/mkt1234/items/b852eeeb213e46702236 にある方法でインストールしました。

Rails をインストールするときのオプションに `-css=bootstrap` を追加しました(Bootstrap のインストールはこれが一番楽だと思う)。

```shell
docker compose run --rm --no-deps web rails new . --force --css=bootstrap -d=postgresql
```

# ひな形を作成

まずは scaffold で User を作成します。

```shell
docker compose run --rm web rails g scaffold User name:string
```

# Bootstrap Toasts の作成

各ページに Toasts を埋め込むのは大変なので、body の直下に 1 つだけ埋め込んで、render の notice で制御するようにしました。
notice と flash の使い分けが良く分からなかったので notice を使っています。

## Toasts 用の erb を作成

notice の有無で data-controller 属性の表示を切り替えるようにしました(他に良いアイデアが思い浮かばなかった……)。
この data-controller 属性がポイントのようです。

`position-fixed start-50 translate-middle-x` は位置調整なのでお好みで。

```erb:app/views/common/_toast.html.erb
<div class="toast position-fixed start-50 translate-middle-x"
    role="alert" aria-live="assertive" aria-atomic="true"
    <%= !!notice && "data-controller=toast" %>>
  <div class="toast-header">
    <strong class="me-auto">成功</strong>
    <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
  </div>
  <div class="toast-body">更新成功</div>
</div>
```

## Toasts を body の下に配置

body の下に _toast.html.erb を配置します。
```erb:app/views/layouts/application.html.erb
...
  <body>
    <%= render "common/toast" %>  
    <%= yield %>
  </body>
...
```

## 既存の notice の表示を削除

notice の文言は Toasts に置き換えるので、各 erb の先頭にある `<p style="color: green"><%= notice %></p>` を消します(index.html.erbの先頭のやつも消しておく)。
```erb:app/views/users/show.html.erb
<%= render @user %>

<div>
  <%= link_to "Edit this user", edit_user_path(@user) %> |
  <%= link_to "Back to users", users_path %>

  <%= button_to "Destroy this user", @user, method: :delete %>
</div>
```

## Stimulus の設定

Stimulus の Controller を作ります(言い方あっているのか？)。
ここが一番の苦労したポイント。

data-controller="toast" を指定した要素がロードされた瞬間に、この controller(toast_controller.js) の `connect()` が呼ばれるので、そこで Toasts を初期化します。

初期化したらすぐに `.show()` を呼んで Toasts を表示させます。


```js:app/javascript/controllers/toast_controller.js
import { Controller } from '@hotwired/stimulus'
import * as bootstrap from 'bootstrap'

export default class extends Controller {
  connect() {
    new bootstrap.Toast(this.element, { delay: 2000 }).show()
  }
}
```

toast_controller を読み込みます。

```js:app/javascript/controllers/index.js
import { application } from './application'

import ToastController from './toast_controller'
application.register('toast', ToastController)
```

## precompile

app/javascript 以下のファイルを precompile します。
自分で package.json に sciprts.build などを作っていれば `docker compose run --rm web yarn build` の方が速いです。

```sh
docker compose run --rm web rails assets:precompile
```

## 表示

作成・更新成功時に Toasts が表示できるようになりました。
あとは自分好みに Toasts のスタイルを変えるだけ。
![toasts.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1814940/483c98e4-7173-07fc-ffd8-06b5a77b7d53.png)