#はい!僕です!!
railsでポートフォリオを作成しています。
表題の問題にぶち当たり半日消耗したので、記録しておく。
#環境
ruby2.7
rails6.0
#問題の詳細
例えばblogs_controllerのcreateアクションを実装するとして、バリデーションが失敗するとrenderで新規投稿画面をrenderするコードを書きます。
def create
@blog = current_user.blogs.build(blog_params)
if @blog.save
flash[:success] = "投稿完了!!"
redirect_to @blog
else
render "new"
flash.now[:alert] = "失敗しました"
end
end
投稿が失敗するとフラッシュメッセージとともにnew画面が表示されます。
ここでリロードするとルーティングエラーかもしくは別画面が表示されます。
#なぜか?〜renderは画面の描写だけ
例の流れは
①新規作成画面のURLはblogs/new
②投稿するとcreateアクションが作動する。
③投稿が失敗する
④renderで画面はnew
だがrenderは画面を描写しているだけなので、URLはcreate
アクションのURLblogs
になっている。
⑤リロードやF5とか押しちゃうと、blogs
をgetしちゃうのでindexアクションが動く。(ルーティングを設定してなかったら、ルーティングエラーが発生)
blogs GET /blogs(.:format) blogs#index
POST /blogs(.:format) blogs#create
new_blog GET /blogs/new(.:format) blogs#new
edit_blog GET /blogs/:id/edit(.:format) blogs#edit
blog GET /blogs/:id(.:format) blogs#show
PATCH /blogs/:id(.:format) blogs#update
PUT /blogs/:id(.:format) blogs#update
DELETE /blogs/:id(.:format) blogs#destroy
#解決策
①redirect_toを使う
renderを使っていることで起きているエラーなので、redirect_to
を使う
def create
@blog = current_user.blogs.build(blog_params)
if @blog.save
flash[:success] = "投稿完了!!"
redirect_to @blog
else
redirect_to new_blog_path
flash.now[:alert] = "失敗しました"
end
end
こうするとURLの問題は解決する。
ただ新たな問題としてフォームに入力していたものは消えてしまう。
(余談だけど、railsチュートリアルみたいにパーシャルを使ってエラーメッセージを描写している場合はそれも表示されなくなる)
この場合の対策はこちらの方の記事にあった
https://qiita.com/yuyasat/items/49e3296f3c64fccc7811
②JavaScriptを使う
僕はこの方法で解決した。
①新しいJavaScriptファイルを作成して、その中でhistory.replaceStateを使う。
history.replaceState('', '', '/blogs/new')
history.replaceStateとは
現在のブラウザの履歴を更新する。
第3引数でURLを入力する。
createアクションの失敗した際、上述したように、画面はviews/new
、URL/blogs
になっている。
この画面とURLの差異で問題が起きているので、現在のURL/blogs
をblogs/new
に変えます。
②applocation.jsに設定する
そのまま引用させていただきます。
対象のview用にコンパイルを行うために、新しいコンパイル用の設定をapp/javascript/packs/application.jsに作成する。
.
.
.
require("blogs/render")
ちなみにコンパイルの言葉の意味がいまいちわかりません。
コンパイルってなんやねん・・・?
https://employment.en-japan.com/tenshoku-daijiten/14875/
コンパイルとは、プログラミング言語で書かれた文字列(ソースコード)を、コンピュータ上で実行可能な形式(オブジェクトコード)に変換することです。
・・・後回しで・・
③対象のviewに埋め込む
<div class="container">
<h2>新規作成</h2>
<%= form_with model: @blog, local:true do |form| %>
<%= render '/shared/error_messages', object: form.object %>
.
.
.
.
<% end %>
</div>
# 下記を追加
<% if @blog.errors.any? %>
<%= javascript_pack_tag 'blogs/render' %>
<%end%>
これはバリデーションエラーがあったら、renderするときにjsを読み込んでね
ということを記述している。
読み込まれたjsは当然①のURLを変更するため、urlがblogs/new
になる。
#課題
解決はしたけど、問題は諸々ある
①とりあえず初回投稿失敗時にめちゃくちゃ時間かかる。
②各リソースごとにjsファイルを用意しないといけない
他に方法はあるのかもしれないけど、ユーザー新規登録にも同様の実装をしており、バリデーションをかけるものすべてに、やるのはDRYじゃない気がする(ただの直感)
このあたりは今後考えていく。
#参考
https://laptrinhx.com/rails-render-houniurlga-bianwatteshimaukotoheno-dui-chu-fa-2616278188/
https://qiita.com/yuyasat/items/49e3296f3c64fccc7811
https://developer.mozilla.org/ja/docs/Web/API/History/replaceState