4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】なんでsaveに成功したらredirect_toで失敗したらrenderなの?

Posted at

はじめに

Railsで開発中、フォームの実装時にrenderの扱いについて悩むことがあったため、調べてく内にredirect_toとrenderの違いを改めて再確認出来たので、備忘として残しておくものです。
ついでにrenderで一瞬悩んだことも書いておきます。(具体的にはbefore_actionの扱い)

環境

・Ruby 2.6.5
・Rails 6.0.3

状況

飲食店情報を登録する様なアプリケーションを作っており、コントローラーに以下の様なアクションを設定していました。

shops_controller.rb
 def new
    @shop = Shop.new
 end

 def create
    @shop = @owner.shops.new(shop_params)
    if @shop.save
      redirect_to :index
    else
      render :new
    end
  end

*ストロングパラメータは今回関係ないので割愛

もはや何も考えずこう書いていましたが、保存に失敗した場合、renderを用いてもう一度投稿フォームに戻すのは何故か?という所を振り返ってみます。

結論としては、入力した内容を保持したままもう一度入力フォームに戻したいからです。

では何故その場合redirect_toではなくrenderを使えば実現できるのでしょうか?
redirect_toとrenderの挙動について振り返ってみます。

redirect_toとは

redircet_toは指定したアクション(やパス)へのアクセスをクライアントサイドに行わせる処理です。
これにより、MVCの流れを再度踏まえた上でそのページにアクセスすることになります。
具体的には「指定されたアクションに再度アクセス⇨コントローラーのアクションを実行⇨対応するビューを表示」という流れですね。

アクションのビューにurlからアクセスする際の挙動と全く同じことをやってくれるわけです。

renderとは

それに対しrenderは、アクションを再度経由することなく指定したアクションに対応するビューをただ表示してくれます。
redirect_toと違い、newアクションの中身を実行することなくビューを返してくれるわけです。

renderで入力内容が保持される訳

先程の説明を踏まえてもう一度コードを見てみましょう。

shops_controller.rb
 def new
    @shop = Shop.new
 end

 def create
    @shop = @owner.shops.new(shop_params)
    if @shop.save
      redirect_to :index
    else
      render :new
    end
  end

newアクションはお店情報の新規登録ページですので、アクション内で@shop = Shop.newを定義しています。(空のインスタンスを生成)
この後フォームに入力した値がparamsとして渡され、それがcreateアクションで今回作成した@shopの情報としてうまく渡されることで保存ができるわけです。

保存に失敗した場合(elseの場合)またフォームに戻す処理をする訳ですが、この時にredirect_toを使うと、再度newアクションが実行され@shopはまた空のインスタンスになってしまいます。

一方でrenderを使うとnewアクションを介さないため、@shopにはcreateアクションで受け取ったparamsの値が保持されたまま、再度フォームのビューを表示してくれる訳です。

new.html.haml

.shop-wrapper
  = form_with model: [@owner, @shop], html: {class: "shopform"}, local: true do |f|

上記はビューの抜粋ですが、フォームは@owner@shopを受け取って内容を表示しているため、@shopに中身がある状態であればその中身をちゃんと表示してくれる訳です。

以上がcreateアクションに失敗した時に、renderを使うべき理由になります。

今回遭遇したエラー

ちなみに今回はrenderを設定した際、最初以下のエラーに遭遇しました。
スクリーンショット 2020-06-26 21.45.44.png

一瞬なんだこれってなったんですが、newアクションに対して以下のbefore_acitonを設定してたんですよね。

shops_controller.rb

before_action :set_select_lists, only: [:new]

# 中略

private
def set_select_lists
    @stations = Station.all.map {|station| [station.name, station.id] }.unshift(["以下から選んでください", nil])
    @genres = Genre.all.map {|genre| [genre.name, genre.id] }.unshift(["以下から選んでください(必須)", nil])
    @marks =  Mark.all.map {|mark| [mark.favorite, mark.id] }.unshift(["以下から選んでください(必須)", nil])
  end

フォームの中にあるプルダウンの選択リストを作り、before_actionで設定していたんです。

先ほども書いた様に、renderはnewアクションを再度経由しないため、before_actionも行わない→設定された値がなくビュー表示でエラーが起きるという流れだったわけです。

これを受け一瞬、「@shop意外に定義しなきゃいけない変数があったらrender使えないの?」って思ったんですが、数分考えてすぐ解決しました。

createアクションを以下の様に書き換えます。

shops_controller.rb
def create
    @shop = @owner.shops.new(shop_params)
    if @shop.save
    else
      set_select_lists
      render :new
    end
  end

createアクションでrenderする前に、before_actionで行っていたset_select_listsを行わせてあげることで、その値も取得した状態でrenderすることが出来ます(renderはあくまでcreateアクションを行った上でnewのビューを表示しているため)

一瞬???となったんですがそんな複雑な話でもなかったですね。

終わりに

実はredirect_toとrenderの挙動は、Progateで初めてプログラミングに触れ1ヶ月程度で一番悩みぬいた所だったんですよね。ここの違いに気づいた時にMVCへの理解も同時に深まったというイメージがありました。

僕と同じ様な初学者の方に何かの参考になればと思います。

*初学者ゆえ、何かあればご指摘いただけると大変ありがたいです。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?