前提
環境は以下の通りです。
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が固定されたように見せる。)
解決方法
手順
- javascriptを作成
- 対象のview(ユーザー登録のview)でjavascriptを使えるようにする
- 対象のviewのみにタグを埋め込む
1. jsファイル作成
app/javascript/packs以下の任意のディレクトリもしくは直下任意の名前(ここではusers/signup_render.js)で下記のjsを作成。
history.replaceState('', '', '/signup')
以下を参考にしました。
replaceStateについてわかりやすい解説
replaceStateについて詳しく
history.replaceState()メソッドで現在の履歴を編集し、メソッドに渡されたURLパラメータによって/users
を/signup
に置き換えています。
2. jsを使えるように
対象のview用にコンパイルを行うために、新しいコンパイル用の設定を作成します。app/javascript/packs/application.js
に追記してしまうと全てのページに反映されてしまい都合が悪いので、新たにエントリポイントをして必要なページから呼び出すようにします。
ここでは呼び出すファイルと同じくsignup_renderという名前にしました。
require("users/signup_render")
パスはapp/javascriptを起点として記載します。
このあたりのフォルダ構成などは こちらの説明 がとてもわかりやすかったのです。
3. viewにタグの埋め込み
対象のviewにjavascript_pack_tag
を埋め込みます。チュートリアルの例ではリスト7.20のユーザー登録画面です。
<!-- これが元々のコード ↓ -->
<% 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が変化する
・【Rails】renderを使った時のリロード対策
・newアクションのバリデーションエラー後のURLの挙動とそれに伴うエラーについて
・Ruby on RailsにおけるJavascriptファイルの取り扱い(Rails6)