[Rails] render後にURLが変わってしまうことへの対処法

#前提
環境は以下の通りです。
Mac OS Catalina 10.15.7
Ruby 2.7.1
Rails 6.0.3.3

自分の備忘録もかねて、できるだけわかりやすく記録しようと思います。
問題をわかりやすく示すためにここでは例としてRailsチュートリアルを用います。


具体的には第7章でユーザー登録の機能を実装する際、バリデーションに引っかかって登録失敗後フォーム画面にrenderされるにあたり、URLが`/signup`から`/users`にURLに変わってしまうという問題、つまり

URLが`localhost:3000/signup`から`localhost:3000/users`に変わってしまう
→usersに対応するviewを作っていないためリロードするとエラーが出てしまう

という問題について解決方法を解説します。

#原因
createアクションでユーザー登録に失敗した時に`render 'new'`するということはnewアクションを呼ばず、viewだけを切り替えているということです。
createアクションは/usersなのでアクション後は当然URLは`/users`になります。
このURLと画面の不整合が問題になるというわけです。

#解決方針
controllerのコードなどは変えずにjavascriptでURLを変えることで解決します。対象のviewのみで特定のjsを読み込みます。
（`/users`から`/signup`に変更、つまりアクション後も一見`/signup`のままURLが固定されたように見せる。）


#解決方法
手順

1. javascriptを作成
1. 対象のview(ユーザー登録のview)でjavascriptを使えるようにする
1. 対象のviewのみにタグを埋め込む

##１. jsファイル作成
app/javascript/packs以下の任意のディレクトリもしくは直下任意の名前(ここではusers/signup_render.js)で下記のjsを作成。

```app/javascript/packs/users/signup_render.js
history.replaceState('', '', '/signup')
```

以下を参考にしました。
[replaceStateについてわかりやすい解説](https://qiita.com/MotohiroSiobara/items/9195bd568a86c4ce05b5)
[replaceStateについて詳しく](https://developer.mozilla.org/ja/docs/Web/API/History/replaceState)

history.replaceState()メソッドで現在の履歴を編集し、メソッドに渡されたURLパラメータによって`/users` を`/signup`に置き換えています。

##2.  jsを使えるように
対象のview用にコンパイルを行うために、新しいコンパイル用の設定を作成します。`app/javascript/packs/application.js`に追記してしまうと全てのページに反映されてしまい都合が悪いので、新たにエントリポイントをして必要なページから呼び出すようにします。
ここでは呼び出すファイルと同じくsignup_renderという名前にしました。

```app/javascript/packs/users/signup_render.js

require("users/signup_render")
```
パスはapp/javascriptを起点として記載します。
このあたりのフォルダ構成などは [こちらの説明](https://cloudpack.media/51718) がとてもわかりやすかったのです。


##3. viewにタグの埋め込み
対象のviewに`javascript_pack_tag`を埋め込みます。チュートリアルの例ではリスト7.20のユーザー登録画面です。

```erb:app/views/users/new.html.erb

 <!-- これが元々のコード ↓ -->

<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(model: @user, local: true) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

<!-- 下にこれを追記↓ -->
<% if @user.errors.any? %>
  <%= javascript_pack_tag 'users/signup_render' %>
<% end %>

```

`if @user.errors.any?`で条件を設定しているのは
「バリデーションエラーが発生 = ユーザー登録失敗してrenderされる時だけjsの処理をしてね」ということです。

`<%= javascript_pack_tag 'users/signup_render' %>`は「`app/javascript/packs/users/signup_render.js`を読み込んでね。」という意味です。


以上、参考になれば幸いです。



##その他参考ページ
・[Rails5 render 'new' 後にURLが変化する](https://teratail.com/questions/135588)
・[【Rails】renderを使った時のリロード対策](https://qiita.com/Ryoga_aoym/items/9c11921ef841eb03045b)
・[newアクションのバリデーションエラー後のURLの挙動とそれに伴うエラーについて](https://ja.stackoverflow.com/questions/36794/new%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%A8%E3%83%A9%E3%83%BC%E5%BE%8C%E3%81%AEurl%E3%81%AE%E6%8C%99%E5%8B%95%E3%81%A8%E3%81%9D%E3%82%8C%E3%81%AB%E4%BC%B4%E3%81%86%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)
・[Ruby on RailsにおけるJavascriptファイルの取り扱い(Rails6)](https://nobunobu1717.site/?p=1292)