renderするとURLが変わり、リロード問題に直面するやつ

#はい！僕です！！

railsでポートフォリオを作成しています。

表題の問題にぶち当たり半日消耗したので、記録しておく。

#環境
ruby2.7
rails6.0


#問題の詳細


例えばblogs_controllerのcreateアクションを実装するとして、バリデーションが失敗するとrenderで新規投稿画面をrenderするコードを書きます。

```ruby:blogs_controller.rb

 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画面が表示されます。

<strong>ここでリロードするとルーティングエラーかもしくは別画面が表示されます。

#なぜか？〜renderは画面の描写だけ
例の流れは

①新規作成画面のURLは```blogs/new```
②投稿するとcreateアクションが作動する。
③投稿が失敗する
④renderで画面は```new```だがrenderは画面を描写しているだけなので、URLは```create```アクションのURL```blogs```になっている。
⑤リロードやF5とか押しちゃうと、```blogs```をgetしちゃうのでindexアクションが動く。(ルーティングを設定してなかったら、ルーティングエラーが発生)

```rails.routesコマンド
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
```

#解決策
<h3>①redirect_toを使う

renderを使っていることで起きているエラーなので、```redirect_to```を使う


```ruby:blogs_controller.rb

 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


<h3>②JavaScriptを使う

僕はこの方法で解決した。

①新しいJavaScriptファイルを作成して、その中でhistory.replaceStateを使う。

```js:app/javascript/blogs/render.js
history.replaceState('', '', '/blogs/new')

```

<h4>history.replaceStateとは

現在のブラウザの履歴を更新する。
第３引数でURLを入力する。

createアクションの失敗した際、上述したように、画面は```views/new```、URL```/blogs```になっている。

この画面とURLの差異で問題が起きているので、現在のURL```/blogs```
を```blogs/new```に変えます。

②applocation.jsに設定する
そのまま引用させていただきます。
>対象のview用にコンパイルを行うために、新しいコンパイル用の設定をapp/javascript/packs/application.jsに作成する。

```js:app/javascript/packs/application.js
.
.
.

require("blogs/render")

```


ちなみにコンパイルの言葉の意味がいまいちわかりません。
コンパイルってなんやねん・・・？
>https://employment.en-japan.com/tenshoku-daijiten/14875/
コンパイルとは、プログラミング言語で書かれた文字列（ソースコード）を、コンピュータ上で実行可能な形式（オブジェクトコード）に変換することです。

・・・後回しで・・


③対象のviewに埋め込む

```ruby:app/views/blogs/new.html.erb
<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を読み込んでね```ということを記述している。

読み込まれたｊｓは当然①のURLを変更するため、urlが```blogs/new```になる。


#課題
解決はしたけど、問題は諸々ある

①とりあえず初回投稿失敗時にめちゃくちゃ時間かかる。

②各リソースごとにｊｓファイルを用意しないといけない
他に方法はあるのかもしれないけど、ユーザー新規登録にも同様の実装をしており、バリデーションをかけるものすべてに、やるのは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