【rails】create/update時に特有のvalidationの実装
RailsのActiveRecordのお話です。
recordを新しく作成する時にだけかけたいvalidationがあったり、更新する時にだけかけたいvalidationがあった経験はありませんか?
そんな時、ActiveRecordはとってもお利口さん。
対象のクラスにおいて、魔法のメソッド「validate_on_create」「validate_on_update」メソッドを定義してあげると、上記を実現できます。
- 例えば、以下のような状況設定とする。
Studentテーブル └ 作成するときだけ、パスワード(password)を確実に登録してもらう。 └ 更新する時だけ、年齢(old)を入力してもらう。
- 実現方法は多数ありますが、代表的なものを2つほど紹介します。
実現方法1
class Student < ActiveRecord::Base validates_presence_of :password, :if => Proc.new{|p| |p.new_record?} validates_presence_of :old, :if => Proc.new{|p| !p,new_record?} end
- ちなみに、このifの条件が複雑になった場合はProcを使わず、メソッドを定義してあげることも可能
class Student < ActiveRecord::Base validates_presence_of :password, :if => :password_required? def password_required? #判定ロジック end end
実現方法2
class Student < ActiveRecord::Base def validate_on_create validates_presence_of :password end def validate_on_update validates_presence_of :old end end
validate_on_updateの仕組み
- 前提としてARオブジェクトをsaveする際、以下のcallbacksが呼び出されます。
(-) save (-) valid? (1) before_validation (2) before_validation_on_create (-) validate (-) validate_on_create (3) after_validation (4) after_validation_on_create (5) before_save (6) before_create (-) create (7) after_create (8) after_save
- この中で、今回はvalid?メソッドに着目して、validations.rbを見ていきます。
- callbacksはまたの機会にまとめます。
# https://github.com/rails/rails/blob/v1.2.3/activerecord/lib/active_record/validations.rb#L786 def valid? errors.clear run_validations(:validate) validate if new_record? run_validations(:validate_on_create) validate_on_create else run_validations(:validate_on_update) validate_on_update end errors.empty? end # https://github.com/rails/rails/blob/v1.2.3/activerecord/lib/active_record/validations.rb#L823 def run_validations(validation_method) validations = self.class.read_inheritable_attribute(validation_method.to_sym) if validations.nil? then return end validations.each do |validation| if validation.is_a?(Symbol) self.send(validation) elsif validation.is_a?(String) eval(validation, binding) elsif validation_block?(validation) validation.call(self) elsif validation_class?(validation, validation_method) validation.send(validation_method, self) else raise( ActiveRecordError, "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " + "class implementing a static validation method" ) end end end
- valid?メソッドを見るとわかる通り、まずはvalidateメソッドが走り、次に対象がnew_recordかどうか判定してvalidate_on_createかvalida_on_updateを走らせるかを決定しています。
- つまり簡単な流れは以下のような流れになる。
- このように定義されているために、ARを継承しているStudentクラスでvalidate_on_updateメソッドを定義すると、更新時にだけ「validates_presence_of」が呼び出された訳ですね!
- ちなみに、validates_presence_ofのメソッドも面白かったので転記
# https://github.com/rails/rails/blob/v1.2.3/activerecord/lib/active_record/validations.rb#L400 def validates_presence_of(*attr_names) configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save } configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) # can't use validates_each here, because it cannot cope with nonexistent attributes, # while errors.add_on_empty can attr_names.each do |attr_name| send(validation_method(configuration[:on])) do |record| unless configuration[:if] and not evaluate_condition(configuration[:if], record) record.errors.add_on_blank(attr_name,configuration[:message]) end end end end
-
- errors.add_on_blank?って何だ?今までerrors.addしか知らなかったよ。。
# https://github.com/rails/rails/blob/v1.2.3/activerecord/lib/active_record/validations.rb#L63 def add_on_blank(attributes, msg = @@default_error_messages[:blank]) for attr in [attributes].flatten value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s] add(attr, msg) if value.blank? end end
-
- 要はvalueがblank?の時にaddする訳ですね。fmfm。どこかで使えそう!