【Rails】Scaffoldされたコントローラを読んでみる
こんにちは、MUGENUPの倉成です。 僕はRailsを使い始めてあと少しで丸2年になるのですが、初めの頃は覚えることがたくさんあって、とても大変だった記憶があります。
今回はそんな中でも、Railsで初めてWebプログラミングをする人向けに、コントローラのアクションであるnew
,create
,show
について解説してみたいと思います。
この記事ではソースコードは説明のため、実際にscaffoldされたコードを抜粋、もしくは省略されているコードを挿入して取り上げます。 なお、使用するコードは
rails g scaffold user name:string email:string postal_code:string address:string
で生成されたコードを元にしており、モデル・ビュー・コントローラはこれを使うことにします。
それではnew
から見て行きましょう。
def new
: 新規登録フォームを作成
def new # (1) Userオブジェクトを生成 @user = User.new # (2) フォームを生成(Rails4では省略されている) render :new end
このアクションでは(1)
で@user
を作成し、(2)
では(1)
で生成したオブジェクト(@user
)とテンプレート(new.html.erb
)を使ってhtmlを生成しています。
図にすると以下の様なイメージです。
今回は@user
の全ての属性の初期状態がnilなので、空のinputフォームが生成されています。
さて、次はcreate
アクションを見て行きましょう。
def create
: 登録 or 差戻し
scaffoldされたcontrollerは複雑で悩むポイントはたくさんあると思うのですが、特に難しいのが行数の多いcreate
だと思います。
今回は簡単のためformat.html
やformat.json
そしてnotice
は一旦脇に置いておいて
def create # (1) フォームから渡されたパラメータを使用し、Userオブジェクトを生成 @user = User.new(user_params) # (2) DBへ保存(を試みる) if @user.save # (3) 新規登録されたユーザーページヘリダイレクト redirect_to @user else # (3') @userを使って、もう一度フォームを表示 render :new end end
のコードを追いましょう。
(1) @userを生成
「@user
ならnew
するときに作ったよ」と思うかもしれません。
しかし、ブラウザからリクエストとして送られてくるのは、フォームに入力されたテキストデータのみのため、改めて@user
を生成する必要があります。
同じコントローラでも「一回ごとのリクエストでインスタンス変数が共有化されない」というのはWebアプリが初めてだと取っ付きづらく感じる原因かもしれないですね。
create
内で、フォームからの情報を使って、@user
を作ります。
(2) DBへ保存
(1)
で生成した@user
の値を検証し、DBへの保存が成功した場合には、(3)
でredirect_to
、失敗した時には(3')
でrender
をします。
saveが失敗するケースとしては「ユーザー名が空白です」「郵便番号は7桁で入力して下さい」のように、バリデーションに引っかかることが多いかと思います。
(3)登録に成功: 登録内容を表示する
ユーザーの登録に成功したら、そのユーザーの登録内容を表示します。
ここでは登録に成功した@user
のページを表示するため
redirect_to @user # 以下と同じ意味 # redirect_to user_path(@user) # redirect_to /users/#{@user.id}
としています。
しかしnew
にあったrender
と違い、redirect_to
はテンプレートを使用してHTMLを生成するわけではありません。
redirect_to
はブラウザに「localhost:3000/users/1
にアクセスしなおしてね」と教えるだけです(urlはドメインがlocalhost:3000
、@user.id=1
の場合)。
ブラウザはサーバーからのredirect
の指示に従ってlocalhost:3000/users/1
にアクセスすることで、詳細ページが表示されることになります。
render
じゃないのはなぜ?
さて、ここでredirect_to
を使用していて、render :show
としないのはなぜでしょう?
上の図でも真ん中の2本の矢印は不要に思えますし、create
内で@user
が既に存在しているので、再度DBから読み直すのは無駄な気がします。
実は、saveに成功時にrender :show
としてしまうと、ページをリロードするたび、新規ユーザー登録処理が発生してしまうという問題があります。
Railsの視点から説明すると、ブラウザをリロードした時には、最後にrender
を呼び出したアクション、今回の場合はcreate
アクションが呼ばれてしまうことになります。*1。
下はcreate
アクションでrender :show
をした後、ブラウザをリロードさせた図です。多重登録を防ぐため「フォーム再送信の確認」ダイアログが表示されているのが分かるかと思います。
今回はscaffoldのコードに習いshow
にリダイレクトしましたが、もちろん一覧ページであるindex
に飛んでもいいですし、ホームのページ(localhost:3000/
)に飛ぶことも多いと思います。
DBの更新が発生した後には、ページを表示するだけのアクションにリダイレクトするということを覚えておきましょう。
また、このpost → redirect → getの一連の流れはその頭文字を取ってPRGパターンと呼ばれているようです。この記事の説明でしっくりこないなぁという方は、ぜひPRGパターンで検索してみてください。
(3')登録に失敗: もう一度入力フォームを表示する
入力内容に不備があった場合、もう一度フォームを表示して、再度情報を入力してもらうため、フォームのテンプレートであるnew.html.erb
をrender
します。create
アクションでrender :new
をすると、保存に失敗した@user
のインスタンス変数をそのまま使えるため、不正な項目のみを再度入力してもらうことが出来ます。
redirect_to
じゃないのはなぜ?
一方、再度フォーム画面を表示さるためにredirect_to "users/new"
を使ってしまうとフォームは表示させることが出来るのですが、ユーザーがこれまでに入力した内容が全て消え、再度全ての項目を入力してもらわなければなりません。
new
の処理を思い出すと、@user
の生成時に@user = User.new
としているので、白紙のフォームが表示されてしまうのは納得できるかと思います。
ユーザー登録時に「一箇所ミスがあるくらいで、全ての内容を再入力させるなー!」という経験は一度くらいあるはずですが、このようなシステムを作らないためにはredirect_to
ではなくrender
を使う必要があるのです。
def show
: 登録情報を表示
ユーザーの情報を表示するのがshow
の役割です。
create
からredirect
してくる場合でも、create
で使用した@user
は使用できないため、改めてDBからデータを取得し、@user
を生成する必要があります。
def show # Rails4ではbefore_actionで呼ばれる # (1) params[:id]のユーザーを取得 @user = User.find(params[:id]) # (2)Viewを生成(Rails4では省略されている) render :show end
@user
を作って、View(show.html.erb
)のhtmlを生成するということで、フォームなのかテキストなのかという違いがあれど、図はnew
と似たものになります。
最後に
さて、今回は僕がRails初心者だった頃に「render
とredirect_to
は何が違うの?」とか「ここの@user
って、こっちのアクションで使えないの?」とか、よく混乱していた部分を中心にRailsのコントローラを解説してみました。
この記事を読んで少しでも理解が深まれば嬉しいです。
この記事はDavid Griffiths著『Head First Rails』の3章を参考にさせていただきました。 この本はRails2系が対象と、やや古くRails3/4では、写経したコードが動かないことが多々あるのですが、Railsの仕組みを知る上ではかなり良書だと思います。 リファレンス的には使用できず、回り道に感じるHead First Railsですが、Railsで初めてWebプログラミングをするという方はこの本から始めてみるとよいかもしれません。