発生した不具合
2つの違いに気がつくこととなった契機、
「新規投稿時にcreateメソッドの成否で条件分岐をさせると全て成功判定となってしまう」
ということについて説明します。
想定していた挙動
新規プロトタイプを作成するため、フォームへ情報を入力し、コントローラーにてDBへの情報登録を行おうという場面です。
新規登録が成功すればroot_pathへ、バリデーションにより失敗すれば新規投稿ページへ遷移するというものです。
以下に2通り描いてみました。
①new+saveメソッド
実に馴染み深い記述ですね。
def create
@prototype = Prototype.new(prototype_params)
if @prototype.save
redirect_to root_path
else
render new_prototype_path
end
end
まずはじめに、フォームで入力された値を取得し、@prototypeに代入しています。
次にif文の条件式にて、DBへの保存がうまくいけばトップページへ、失敗すれば新規投稿画面へリダイレクトします。
こちらの記述では、想定通りの挙動を確認できます。
②createメソッド
それではcreateメソッドで記述をした場合はどうなるでしょうか?
def create
if Prototype.create(prototype_params)
redirect_to root_path
else
render new_prototype_path
end
end
こちらはcreateメソッドを用いて、DBへ保存できたらトップページへ、失敗したら新規投稿画面へのリダイレクトしようというものです。
一見new+saveメソッドの時と代わりなさそうですね。
ですがこの記述、新規投稿画面で何も入力せずにフォームから情報を送った場合にもトップページへリダイレクトする(= if文でtrue判定) という不具合が起こってします。
本来であればバリデーションに引っかかるためDBへ保存できず、createメソッドが失敗し新規投稿画面へリダイレクトするはずです。
一体どういうことでしょうか?
実は返り値が異なる
実際何が起こっているのか、みんな大好きbinding.pryを用いて確認してみました。
何も入力せず新規投稿ボタンを押し、createアクションの最初で処理を止めた状態です。
(def createの直下です)
[1] pry(<PrototypesController>)> @prototype = Prototype.new(prototype_params)
=> <Prototype:0x00007fe4375c1520 id: nil, title: "", catch_copy: "", concept: "", user_id: 1, created_at: nil, updated_at: nil>
[2] pry(<PrototypesController>)> @prototype.save
(0.4ms) BEGIN
↳ (pry):6:in `create'
User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
↳ (pry):6:in `create'
(0.3ms) ROLLBACK
↳ (pry):6:in `create'
=> false
[7] pry(<PrototypesController>)> Prototype.create(prototype_params)
(0.3ms) BEGIN
↳ (pry):11:in `create'
User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
↳ (pry):11:in `create'
(0.3ms) ROLLBACK
↳ (pry):11:in `create'
=> <Prototype:0x00007fe436ef9260 id: nil, title: "", catch_copy: "", concept: "", user_id: 1, created_at: nil, updated_at: nil>
両者の実行結果を見比べると一目瞭然です。(一番下の行)
①のnew+saveメソッドの場合は最終的な戻り値としてfalseを返しています。
もちろんバリデーションに弾かれているので、DBへの保存はされていません。
ですが、②の場合falseが返ってきているわけではありません。
実はインスタンスを返り値としているんですね。
なのでif文の条件分岐においてtrue判定となり、DBへ保存できていないのにトップページへ遷移してしまっていました。
結論
・saveメソッドは戻り値としてT/Fを返すが、createメソッドはインスタンスを返すのでtrue判定になっていた。
お恥ずかしながら、普段返り値を意識しないまま記述をしてしまっていたので、今後は気をつけたいと思います。