Hotwire.love meetup Vol.21(2023-09-14)のフリートークにて、「Modelに対するValidation定義とは異なるDBのカラム制約を与えた状態で、DBに保存するときに制約違反が発生しても、500エラーページとして表示されない」というテーマで盛り上がりました。
ただし、最終的な結論がでずに時間切れとなりました。
今まではsave!メソッドを呼び出し、制約違反の例外が発生したらエラーページが表示されていたが、最近画面が何も変化しないことに遭遇した、Turboが握りつぶしているのではないかという疑問でした。
いくつかのやり方でコード変更し、挙動が変わることは確認しましたが、なぜエラーページが表示されないかには辿り着けませんでした。
勉強会の後、Railsのエラーページ表示の仕組みを色々調べて、やっと腑に落ちる状態になりました。フリートーク中に出された色々な仮説に基づくコード変更の根拠が理解出来るようにはなったのですが、今まで出来ていたのに最近できなくなったというという事に対しては、説明できないとも感じました。
そこで思いついたのは、「実はDBカラムに対する制約違反は発生していない」という仮説です。
そもそも制約違反が発生していないのならば、エラーページは表示されません。
「制約違反が発生している」という前提で話していましたが、db/schema.rbをみて確認したり、railsのコンソールでsaveメソッドを呼び出して例外が発生するのかを確認してはいませんでした。またValidationが通った後にDBに保存するのですから、Validationが通らなかった場合は、保存しないため制約違反も発生しません。
実際に、Ruby 3.2.2, Rails 7.0.8、PostgrSQLにて
rails g scaffold blog title:string content:
とし、db/schema.rbが以下の通りになるようにmigrateして、試してみました。
ActiveRecord::Schema[7.0].define(version: 2023_09_17_002252) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "blogs", force: :cascade do |t|
t.string "title", null: false
t.text "content", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
簡単にするため、Modelに対してはValidationを定義しませんでした。
Not Null制約を与えているので、blog_paramsではなく(フォームから送信されたパラメータでは値がnilではなく空文字列になってしまうため)明示的に値がnilであるハッシュをsaveメソッドに与えて呼び出しました。
def create
hash = { title: nil, content: nil }
# @blog = Blog.new(blog_params)
@blog = Blog.new(hash)
# raise
# @blog = Blog.find(100)
# @blog.save! # (A)
respond_to do |format|
if @blog.save # (B)
# if @blog.valid? # (A)の場合はこちらを有効にしました
format.html { redirect_to blog_url(@blog), notice: "Blog was successfully created." }
format.json { render :show, status: :created, location: @blog }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @blog.errors, status: :unprocessable_entity }
end
end
end
結果は、(A)の場合はsave!メソッド呼び出し時、(B)の場合saveメソッド呼び出し時に、以下の例外が発生しエラーページが表示されました。
ActiveRecord::NotNullViolation in BlogsController#create
saveメソッドはDB保存に成功したらtrue、失敗したらfalseを返します。
Validation結果はerrorsに格納されています。
save!メソッドは、DB保存に失敗したら、RecordNotSavedエラーをraiseします。
saveとsave!の差は成功、失敗を値として返すか、失敗時に例外をraisesするかであり、呼び出し先でraiseされた例外はrescueしないようです。