Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: 個別のバリデーションエラーをErrorオブジェクトにカプセル化する(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rails: 個別のバリデーションエラーをErrorオブジェクトにカプセル化する(翻訳)

モデルのsavecreateupdateアクションが失敗した場合のRailsのerrorsの表現方法が#32313で変更されました。

変更前

たとえばUserというモデルがあり、そこにfirst_namelast_namecontact_numberemailなどのカラムがあり、どれも必須だとしましょう。Userオブジェクトを作成するときにfirst_namecontact_numberstringとして渡さないと、#errors関数が以下のようなエラーを出力します。

class User < ApplicationRecord
  validates :contact_number,
            presence: true,
            numericality: true,
            length: { :minimum => 10, :maximum => 15 }
end

user = User.create(email: "sam@example.com", last_name: "Example", contact_number: "abcdefghijk")

user.errors
=> #<ActiveModel::Errors:0x00007fe42c1650b8
  @base=
  #<User:0x00007fe42c1676d8
  ....
  ....
  @details={:first_name=>[{:error=>:blank}], :contact_number=>[{:error=>:not_a_number}]}
  @messages=
  {:first_name=>["First Name is required."],
   :contact_number=>["Contact number is not a number."]
  }>

特定のフィールドに関するエラーメッセージは以下のように[]メソッドでアクセスできます。

user.errors[:first_name]
=> ["First Name is required."]

#messages#full_messagesですべてのエラーメッセージのリストを表示することもできます。

user.errors.messages
{
  :first_name=>["First Name is required."],
  :contact_number=>["Contact number is not a number."]
}

user.errors.full_messages
[
  "First name is required.",
  "Contact number is not a number."
]

エラーへのアクセスを上のように行うのはオブジェクト指向的ではありません。この方法では、特定のフィールドに関連付けられているエラーが複数ある場合に配列インデックスでアクセスしなければならなくなります。

user = User.create(email: "sam@example.com", last_name: "Example")

user.errors[:contact_number]
#=> ["Contact Name is required.", "Contact Name is not a number."]

user.errors[:contact_number][0]
#=> "Contact Name is required."

user.errors[:contact_number][1]
#=> "Contact Name is not a number."

変更後

最近のActiveModel#errorsの変更によって、上のエラーはハッシュではなくErrorクラスのオブジェクトとして出力されます。

user = User.create(email: "sam@example.com", last_name: "Example", contact_number: "abcdefghijk")

user.errors
#=> #<ActiveModel::Errors:0x00007ff5ba2be5a0 @base=#<User id: nil, first_name: nil, last_name: "Example", email: "sam@example.com", contact_number: "abcdefghijk", created_at: nil, updated_at: nil>,
@errors=[<#ActiveModel::Error attribute=first_name, type=blank, options={}>, <#ActiveModel::Error attribute=contact_number, type=not_a_number, options={:value=>"abcdefghijk"}>]>

where句を用いると、特定の属性に関連するエラーをフェッチできます。

user.errors.where(:contact_number)
#=> [<#ActiveModel::Error attribute=contact_number, type=not_a_number, options={:value=>"abcdefghijk}>]

where => (attribute, type, options)とシグネチャが似ているaddadded?deletematch?といったメソッドも利用できます。

user.errors.add(:contact_number, :too_short, count: 10)
#=> <#ActiveModel::Error attribute=contact_number, type=too_short, options={:count=>10}>

user.errors.where(:contact_number)
#=> [<#ActiveModel::Error attribute=contact_number, type=not_a_number, options={:value=>nil}>, <#ActiveModel::Error 
attribute=contact_number, type=too_short, options={:count=>10}>]

user.errors.added?(:contact_number, :too_short, count: 10)
#=> true

user.errors.added?(:contact_number, :too_short)
#=> false

user.errors.delete(:contact_number, :too_short, count: 10)

user.errors.where(:contact_number)
#=> [<#ActiveModel::Error attribute=contact_number, type=not_a_number, options={:value=>nil}>]

user.errors.match?(:contact_number, :not_a_number)
#=> true

user.errors.match?(:contact_number, :too_long)
#=> false

このように、added?メソッドで指定の属性に特定のエラーが発生しているかをチェックできます。

この変更によって、messageメソッドやfull_messageメソッドでwhere句を用いてアクセスできるようになりました。

user.errors.where(:first_name, :blank).last.message
#=> "can't be blank"

user.errors.where(:contact_number, :not_a_number).last.full_message
#=> "Contact number is not a number"

メモ

以下のメソッドは非推奨化されました。利用するとdeprecation warningが表示されます。

  • []
  • each{|attr, msgs|}
  • generate_message
  • has_key?
  • key?
  • keys
  • values
user.errors.keys
DEPRECATION WARNING: ActiveModel::Errors#keys is deprecated and will be removed in Rails 6.2.

To achieve the same use:

  errors.attribute_names

以下のメソッドは変更されていません。

  • as_jsonblank?clearcountempty?
  • add
  • added?
  • full_messages
  • full_messages_for
  • include?sizeto_hashto_xmlto_a
  • messages
  • details

関連記事

Rails 6.1でreturnやbreakやthrowによるトランザクション終了が非推奨化(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。