【Ruby on Rails】Turbo Stream 実行後にredirect_toを行う

## はじめに
Ruby　on Railsを使用して開発を行っていますが、モーダルウィンドウの実装で詰まりました。
備忘録として問題解決までの手順を書き記していきます。
※環境や、知識の足りていないところがあるため、おおまかな情報になります。

## 環境

* Docker
* Ruby 3.3.3
* Rails 7.1.2
* bootstrap 5.3.0
* Hotwire（Turbo Stream)
* stimulus

## やりたかったこと
とあるページのログイン、新規登録処理をモーダルウィドウで行いたかった。
![モーダルウィンドウ実装.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737553/a428c9c0-3eea-15fe-74c2-9e9b1b35a6b6.gif)

・`turbo_flame_tag`を使用し、Bootstrapのモーダルを読み込む。
・バリデーションエラーをモーダルで表示するためsubmitするための`form`に
```Ruby: app/views/user_sessions/_form.html.erb
<%= turbo_frame_tag dom_id(@user, :form) do %>
  <%= render 'error' %>
  # フォームのsubmitが完了した際に発火するjavascriptを指定
  # turbo:submit-end->login_modal#close'
  <%= form_with url: login_path, html: { data: {action: 'turbo:submit-end->login_modal#close'}} do |f| %>
    <div class="w-75 mx-auto">
    <div class="mb-3">
      <%= f.label :email, t('activerecord.attributes.user.email'), class: "form-label" %>
      <%= f.email_field :email, class: "form-control", placeholder: t('placeholder.email') %>
    </div >
    <div class="mb-4">
      <%= f.label :password, t('activerecord.attributes.user.password') ,class: "form-label" %>
      <%= f.password_field :password, class: "form-control", placeholder: t('placeholder.password') %>
    </div >
    <div class="mt-4">
      <%= f.submit t('dictionary.login'), class: "btn btn-success w-100 rounded-3" %>
    </div >
    </div>
  <% end %>
<% end %>

```
* `<%= turbo_frame_tag dom_id(@user, :form) do %>`
→フォームを部分的に読み込ませるための目印
　
* `<%= form_with url: login_path, html: { data: {action: 'turbo:submit-end->login_modal#close'}} do |f| %>`
→フォームの送信完了後に読み込むstimulusで実装した、jsコントローラの指定。今回はapp/javascript/controllers/login_modal_controller.jsの`close`が呼び出される。
```js:app/javascript/controllers/login_modal_controller.js
import { Controller } from "@hotwired/stimulus"
import { Modal } from "bootstrap"

// Connects to data-controller="modal"
//　画面表示時に呼び出される
export default class extends Controller {
  connect() {
    //　すでに開いているモーダルを確認し・閉じる
    var registerModalElement = document.getElementById('registerModal');
    if (registerModalElement !== null ) {
      var modalInstance = Modal.getInstance(registerModalElement);
      if (modalInstance._isShown) {
        modalInstance.hide();
      } 
    }
    //　モーダルを開く
    this.modal = new Modal(this.element)
    this.modal.show()
  }

  //　eventがsuccessの場合にウィンドウを閉じる
  close(event) {
    if (event.detail.success) {
      this.modal.hide()
    }
  }
}
```
上記の設定により、バリデーションエラーが発生しても、画面が閉じられず、モーダルにエラーが表示される。

## 本題　turbo streamのリダイレクト処理
### ■問題点
モーダルを開いたままにしておくために、この処理は`turbo　stram`を使用し、画面の一部を更新する方式をとっている。
そのため正常処理完了後にリダイレクトを行うと、更新対象がturboフレーム内になってしまい画面のリロードが行われない。
```Ruby: app/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
  skip_before_action :require_login, only: %i[new create]

  # モーダルを表示する
  def new
    @user = User.new
  end

  # ログイン処理
  def create
    @user = login(params[:email], params[:password])
    if @user
      # 正常終了
      # --------------------
      #　ここの処理をどうするべきか
      # --------------------
    else
      # バリデーションエラー
      @user = User.new # エラーを保持するためのユーザーオブジェクト
      @user.errors.add(:base, 'メールアドレスまたはパスワードが無効です')
      render :new, status: :unprocessable_entity
    end
  end

  def destroy
    logout
    redirect_to root_path
  end
end
```

### ■解決方法
この問題は`Turbo.visit`を使用することで解消できた。
※今回の方法以外に以下の方法があった
・javascriptを使用する方法
・create.turbo_stram.erb内でリダイレクト用のリンクを作成しjavascriptでリンククリックイベントを発火させる

が、最も簡単そうな以下の方法を用いることにする。
　
1\. `app/javascript/application.js`以下の内容を追加する。
```js: app/javascript/application.js
Turbo.StreamActions.redirect = function () {
  Turbo.visit(this.target);
};　
```
これは`Hotwire`のTurboライブラリの`StreamActions`オブジェクトに、新しいメソッド`redirect`を追加している。
`Turbo.visit`は指定されたURLにブラウザをリダイレクトさせる。
　
2\. controllerを修正する
```Ruby: app/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController

  # ~~~省略~~~

  def create
    @user = login(params[:email], params[:password])
    if @user
      # ▼▼▼この行を追加▼▼▼
      flash[:success] = 'ログイン完了！！'
      render turbo_stream: turbo_stream.action(:redirect, root_path)
      #　　▲▲▲この行を追加▲▲▲
    else
      @user = User.new # エラーを保持するための新しいユーザーオブジェクト
      @user.errors.add(:base, 'メールアドレスまたはパスワードが無効です') 加
      render :new, status: :unprocessable_entity
    end
  end

  # ~~~省略~~~
  
end

```
この処理を追加することで、画面がturboフレームを抜け出し、画面がリロードされる。
またflashメッセージも画面に表示することができる。


## 最後に
意外と簡単な処理のみで実装が行えたが、これに問題があるかは現在わかっていない。
StreamActionsの使い方をもっと勉強しないと、すんなり実装とはいかないなと思いました。

**完全な情報ではない可能性があるため、補足・指摘等ある場合はご教示ください**

## 参考

* turbo stimulusでのモーダルウィンドウの呼び出し

https://www.youtube.com/watch?v=Pu4-xMTb_TQ&ab_channel=JunichiIto

* turbo stream　からのredirect方法について

https://github.com/hotwired/turbo-rails/pull/367