みなさんご存知createメソッド。railsの新規投稿をするときとかに便利ですよね。
一般的に、createメソッドは、newメソッドとsaveメソッドを組み合わせたものという説明がされることが多いですよね。
これなら一つで済むcreateメソッドで統一した方がいいじゃん!って思いませんか?
でもcreateメソッドとnew+saveメソッドって、正確には異なる挙動をするんです。
それによって不具合が起こるケースも、、、
そんなわけで、違いについての仮説を以下で記述します!
createメソッドで条件分岐をすると不具合が出る
2つの違いに気がつくこととなった契機、
「新規投稿時にcreateメソッドの成否で条件分岐をさせると全て成功判定となってしまう」
ということについて説明します。
想定していた挙動
メモ投稿アプリにおいて、新規memoを作成するため、フォームへ情報を入力し、コントローラーにてDBへの情報登録を行おうという場面です。
新規登録が成功すればroot_pathへ、バリデーションにより失敗すれば新規投稿ページへ遷移するというものです。
以下に2通り描いてみました。
それぞれの挙動を見ていきましょう
①new+saveメソッド
createアクション内の記述は次のようになります。
def create
@memo = Memo.new(memo_params)
if @memo.save
redirect_to root_path
else
render new_memo_path
end
end
まずはじめに、フォームで入力された値からnewメソッドでインスタンスを生成し、@memo
に代入しています。
次にif文の条件式にて、DBへの保存(saveメソッド)がうまくいけばトップページへ、失敗すれば新規投稿画面へリダイレクトするような記述です。
こちらの記述では想定通り、DBへ保存がうまく行けばトップページへ、失敗すれば新規投稿ページに遷移します。
②createメソッド
それではnew+saveメソッドの代わりにcreateメソッドで条件分岐をするとどうなるでしょうか?
記述としては次のとおりです。
def create
if Memo.create(memo_params)
redirect_to root_path
else
render new_memo_path
end
end
こちらはcreateメソッドを用いて、DBへ保存できたらトップページへ、失敗したら新規投稿画面へのリダイレクトしようというものです。
一見new+saveメソッドの時と代わりなさそうですね。
ですがこの記述、新規投稿画面で何も入力せずにフォームから情報を送った場合にもトップページへリダイレクトするという不具合が起こってします。
本来であればバリデーションに引っかかるためDBへ保存できず、createメソッドが失敗し新規投稿画面へリダイレクトするはずです。
一体どういうことでしょうか?
実は返り値が異なる!
実際何が起こっているのか、みんな大好きbinding.pryを用いて確認してみました。
フォームでは何も入力せず新規投稿ボタンを押し、createアクションの最初で処理を止めた状態です。
(def createの直下です)
[1] pry(<MemoController>)> @memo = Memo.new(memo_params)
=> <Memo:0x00007fe4375c1520 id: nil, text:"", user_id: 1, created_at: nil, updated_at: nil>
[2] pry(<MEmoController>)> @memo.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(<MemoController>)> Memo.create(Memo_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'
=> <Memo:0x00007fe436ef9260 id: nil, text: "", user_id: 1, created_at: nil, updated_at: nil>
両者の実行結果を見比べると一目瞭然です。(一番下の行)
①のnew+saveメソッドの場合は最終的な戻り値としてfalseを返しています。
一方の②のcreateメソッドの場合も、処理自体は実行されていることがわかります。
もちろんどちらもバリデーションに弾かれているので、DBへの保存はされていません。
ですが、②の場合falseが返ってきているわけではありません。
実はインスタンスを返り値としているんですね。
if文においてfalse判定となるのは、実行結果がfalseだったときやnilだったときです。
②では戻り値がインスタンスであるため、とりあえずfalse以外のものが来ている=if文の条件分岐においてtrue判定となり、DBへ保存できていないのにトップページへ遷移してしまっていました。
この記事のまとめ
- saveメソッドは実行結果にTrue/Falseを返す
- createメソッドは実行結果にインスタンスを返す
- if文の条件式でcreateメソッドを使うと、DBへの保存の成否に関わらずtrue扱いとなるため、意図しない挙動になる
if文で条件分岐をする際は、new+saveメソッドを使うようにしましょうね!
コメント