redirect_toとrenderの違いって?
まずは簡単にrenderとredirect_toの違いを見ていきます
・render: controller→view
・redirect_to: controller→URL→route→controller→view
と言う流れでそれぞれ処理します。よくcreateやupdateのアクションで成功した時にはredirect_to,失敗した時にはrenderを使用すると思います。
そこで問題となってくるのが、renderではrouteを介していないことです。そのため、createが失敗した際に、renderを使用している場合、createのurlのままviewにエラー文が表示されるという形になります。そうすると、そのページでリロードをしたら、リロードはGETが読み込まれるので、createのパスのGETのページあるいはそれがなければルーティングエラーになってしまいます。
文章で説明するよりも見ていただいた方が早いので、簡単なアプリケーションで動きを見てみます。アプリケーションには全てのカラムにpresence: trueのバリデーションを入れています。
はい。renderだとこのような挙動になってしまいますね。実際のサイトとかでバリデーションに引っかかってリロードしたら別のページに飛ぶなんていうことはまずないと思うので、変更してみます
1.renderをredirect_toに変える
まずは問題のrenderの部分をredirect_toに変えます
def create
@item = Item.new(item_params)
@genres = Genre.all
if @item.save
redirect_to admin_item_path(@item.id), notice: "商品の作成に成功しました"
else
redirect_to new_admin_item_path
end
end
2.バリデーションメッセージの取得
redirect_toに変えたことにより、エラーメッセージを取得して表示させることができなくなってしまっているので、取得できるようにします。
def create
@item = Item.new(item_params)
@genres = Genre.all
if @item.save
redirect_to admin_item_path(@item.id), notice: "商品の作成に成功しました"
else
redirect_to new_admin_item_path, flash: { error: @item.errors.full_messages }
end
end
エラーメッセージはフラッシュメッセージとして取得します
3.バリデーションの表示
定義しただけではバリデーションメッセージを表示させることはできないので、viewに表示させるための記述をしていきます。
<div class="container">
<div class="row">
<div class="col-sm-12 col-md-8 col-lg-6 px-5 px-sm-0 mx-auto">
<h2 class="border-bottom">商品新規登録</h2>
<%= form_with model: @item, url: admin_items_path, method: :post, local:true do |f| %>
<% if flash[:error].present? %>
<div id="error_explanation">
<h3><%= flash[:error].count %> 件のエラーがあります</h3>
<ul>
<% flash[:error].each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= f.label "商品画像" %>
<%= f.file_field :image, class: "form-control-file item_image", accept: 'image/*' %>
</div>
<div class="form-group">
<%= f.label "商品名" %>
<%= f.text_field :name, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label "説明文" %>
<%= f.text_field :introduction, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label "ジャンル名" %>
<%= f.select :genre_id, @genres.map{ |genre| [genre.name, @item.genre_id = genre.id]}, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label "税抜価格" %>
<%= f.text_field :price, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label "販売ステータス" %>
<div>
<%= f.label :is_active, "販売中" %>
<%= f.radio_button :is_active, true, class: "form-control" %>
</div>
<div>
<%= f.label :is_active, "販売停止中" %>
<%= f.radio_button :is_active, false, class: "form-control" %>
</div>
</div>
<div class="form-group">
<%= f.submit "商品登録", class: 'btn btn-success' %>
</div>
<% end %>
</div>
</div>
</div>
これでredirect_toでエラーメッセージを取得し、表示させることができるようになりました!ではみてみます。
しっかりとバリデーションメッセージを出した後にリロードするとその画面に戻っています。
まとめ
今回はcreateの失敗の際にrenderではなく、redirect_toで戻る方法をやってみました。urlが変わってしまうと、最悪routingエラーが出てしまうので、renderを使用する際にはそこにも注意が必要です。では今回はこの辺で!また別の記事でお会いしましょう!