モデルでは特に考えずにerrors.add(:base, '名前の文字数オーバー')
とかしておけば良いかーみたいな風潮ありますよね
これ
> user = User.new
> user.errors
=> #<ActiveModel::Errors:0x00007fc0ed8b5a50 @base=#<User id: nil, name: nil, created_at: nil, updated_at: nil>, @messages={}, @details={}>
> user.errors.add(:base, '名前の文字数オーバー')
> user.errors.full_messages
=> ["名前の文字数オーバー"]
そもそも.add
ってなんなのさと、すぐraiseされるならわかるけど、何個も追加(add)できるの???😴
errors.addの使い方一覧
結論から書きます。
# シンプルな構文(どんなエラーなのか)
> user.errors.add(:base, '名前の文字数オーバー')
> user.errors.full_messages
=> ["名前の文字数オーバー"]
# 一般的な構文(どのカラムの、どんなエラーなのか)
> user.errors.add(:name, '文字数オーバー')
> user.errors.full_messages
=> ["Name 文字数オーバー"]
# I18nを利用した構文
# (対象がないと translation missing)
## activerecord.errors.models.user.attributes.name.over_char_limit
## activerecord.errors.models.user.over_char_limit
## activerecord.errors.messages.over_char_limit
## errors.attributes.name.over_char_limit
## errors.messages.over_char_limit
> user.errors.add(:name, :over_char_limit)
> user.errors.full_messages
=> ["Name 文字数オーバー"]
# すぐraiseしたい構文
> user.errors.add(:name, :over_char_limit, strict: true)
ActiveModel::StrictValidationFailed (Name 文字数オーバー)
# StrictValidationFailed < StandardError です
# 何の為にあるのか不明
> user.errors.add(:name, :over, message: '文字数オーバー')
> user.errors.full_messages
=> ["Name 文字数オーバー"]
# .details のため?
> user.errors.details
=> {:name=>[{:error=>:over}]}
すぐraiseできるじゃん!
良い機会なので、ここからerrors
とvalidations
の関係性を考えます。
ここから下は読まなくて良いです。
railsを調べてみた。
rails 5-1-stable
ブランチを見てます!
errorsのmethodはどこから?
すぐ見つけた
active_model/validations.rb
# Errors の引数でモデルのオブジェクトを渡してる
def errors
@errors ||= Errors.new(self)
end
モデルはActiveRecord::Base
を継承しているので、辿っていくと以下のようにincludeされてる
# activerecord/lib/active_record/base.rb
include Validations
# activerecord/lib/active_record/validations.rb
include ActiveModel::Validations
.addのmethodはどうなってるの?
normalize_message
→ I18n
で使えるように頑張ってパースしてた
raiseとかはここで制御してるんだね
active_model/errors.rb
def add(attribute, message = :invalid, options = {})
message = message.call if message.respond_to?(:call)
detail = normalize_detail(message, options)
message = normalize_message(attribute, message, options)
if exception = options[:strict]
exception = ActiveModel::StrictValidationFailed if exception == true
raise exception, full_message(attribute, message)
end
details[attribute.to_sym] << detail
messages[attribute.to_sym] << message
end
.addしたけど、いつraiseされるの?
railsでは.create!
も.update!
も結果的に.save!
が呼ばれます。
perform_validations
のvalid?
でfalseになるとraiseされます。
def save!(options = {})
perform_validations(options) ? super : raise_validation_error
end
private
def raise_validation_error
raise(RecordInvalid.new(self))
end
def perform_validations(options = {})
options[:validate] == false || valid?(options[:context])
end
RecordInvalidが呼び出されたら
errorsの配列のメッセージはここでjoinされてるんだね
class RecordInvalid < ActiveRecordError
def initialize(record = nil)
if record
@record = record
errors = @record.errors.full_messages.join(", ")
message = I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", errors: errors, default: :"errors.messages.record_invalid")
else
message = "Record invalid"
end
super(message)
end
end
以下のja.yml
が必要なので忘れないように!
ja:
activerecord:
errors:
messages:
record_invalid: "%{errors}"
valid?では何をしてるの?
-
errors.clear
: valid?以前に追加されたerrorsを削除 -
run_validations!
: モデルに紐づいてる:validateを1つずつ実行してる -
errors.empty?
: errorsの配列にあればraise!
つまり、コンソール上でerrors.add
をしても何も起きないんです🙄(clearされる)
def valid?(context = nil)
context ||= default_validation_context
output = super(context)
errors.empty? && output
end
def valid?(context = nil)
current_context, self.validation_context = validation_context, context
errors.clear
run_validations!
ensure
self.validation_context = current_context
end
run_validations!
はちょっと難しかった
active_support/callbacks.rb のrun_callbacks
が呼ばれるんだけど、これが結構難しい
(結果的に、:validateを1つずつ実行してるだけなんだけどね)
解説は以下のqiitaにありました
Railsのvalidationの実行過程を調べる
どんな:validateが実行するのか知りたいなら
# モデルに紐づいてる:validateの一覧
> user.__callbacks[:validate]
# 自作validatorの一覧
> user._validators
自作validatorを作りたいなら
これは弊社でもおなじみのvalidatorの作り方です!
オリジナルのバリデーションクラス:validates_with
所感
初めてrailsのソースコード読んでみたけど、結構読みやすかった!
でもproc
とかyield
とか意味は知ってるけど、普段使わない関数が急に出てくると困惑するし、急にわからなくなるなあ。
これを機に俺もrailsのソースコードを読み込んでみよう!と10分思ったが、めんどくさいし、タピオカ飲みたいので辞めた🧸