⚠️

Railsでバリデーションエラー時にカスタム例外を出したい!!

2024/08/07に公開

弊社の開発チームは現在週一出社なんですが、週に一回しかしっかり外出しないとはいえ、この溶けるような暑さに無事やられています。。

そんなことは置いておいて、今回はタイトル通りRails(ActiveRecord)でvalidationでコケた際にカスタム例外をraiseしたくなるときはありませんか??
そんなあなたに必見のTipsを紹介します!!

やりたいこと

以下のようなvalidationを組んでいるとしましょう。

class Hoge < ActiveRecord::Base
  validates :title, presence: true, uniqueness: { case_sensitive: false }
  validate :validate_description

  private

  def validate_description
    errors.add(:description, '何らかのエラー') if description.fuga?
  end
end

そしてこのHogeクラスをcreateする際にvalidationごとになんらかの値を返したいcontrollerがいるとします。
自分がいるチームではinsertやupdateをする際は例外を投げる形を採用しているため、rescueをしてエラーの中身を見て条件分岐する必要があります。
controllerで、しかもrescueで条件分岐なんてできるだけしたくないですよね...

class HogeController < ActionController::Base
  def fuga
    @hoge = Hoge.create!(title: params[:title], description: params[:description])
  rescue ActiveRecord::RecordInvalid => e
    @error = if e.record.errors.details.key?(:title)
               'Title is invalid'
             elsif e.record.errors.details.key?(:description)
               'Description is invalid'
             else
               ''
             end
  end
end

解決方法

Railsガイド#厳密なバリデーションにもある通り、strictを使えばraiseをするクラスを指定できます。
また、Railsガイドにはvalidatesでの指定の仕方は記述されていますが、errors.add()を使ったstrictの使い方は書かれていなかったので少し苦戦しました。

class Hoge < ActiveRecord::Base
+  class TitleInvalidError < StandardError; end
+  class DescriptionInvalidError < StandardError; end

+  validates :title, presence: true, uniqueness: { case_sensitive: false }, strict: TitleInvalidError
-  validates :title, presence: true, uniqueness: { case_sensitive: false }
  validate :validate_description

  private

  def validate_description
+    errors.add(:description, strict: DescriptionInvalidError) if description.fuga?
-    errors.add(:description, '何らかのエラー') if description.fuga?
  end
end
class HogeController < ActionController::Base
  def fuga
    @hoge = Hoge.create!(title: params[:title], description: params[:description])
+  rescue Hoge::TitleInvalidError
+    @error = 'Title is invalid'
+  rescue Hoge::DescriptionInvalidError
+    @error = 'Description is invalid'
-  rescue ActiveRecord::RecordInvalid => e
-    @error = if e.record.errors.details.key?(:title)
-              'Title is invalid'
-            elsif e.record.errors.details.key?(:description)
-              'Description is invalid'
-            else
-              ''
-            end
  end
end

きれいになりましたね!!
暑い夏もstrictを使って涼しげにrecueしていきましょう!!

SMARTCAMP Engineer Blog

Discussion