Railsモデルがあり、フォームを介してユーザーが入力した7つの数値属性があります。
これらの各属性の存在を検証する必要があります。これは明らかに簡単に使用できます。
validates :attribute1, :presence => true
validates :attribute2, :presence => true
# and so on through the attributes
ただし、いくつかの属性を受け取り、それらを使用していくつかの計算を行うカスタムバリデーターも実行する必要があります。これらの計算の結果が特定の範囲内にない場合、モデルは無効であると宣言する必要があります。
それだけでも、これも簡単です
validate :calculations_ok?
def calculations_ok?
errors[:base] << "Not within required range" unless within_required_range?
end
def within_required_range?
# check the calculations and return true or false here
end
ただし、問題は、メソッド「validate」の前にメソッド「validate」が常に実行されることです。これは、ユーザーが必須フィールドの1つを空白のままにした場合、Railsが空白の属性で計算しようとするとエラーをスローすることを意味します。
では、必要なすべての属性の存在を最初に確認するにはどうすればよいですか?
attributes
ハッシュ自体の順序に依存する可能性があるため、これらの検証が実行される順序が保証されているかどうかはわかりません。必要なデータの一部が欠落している場合は、validate
メソッドの回復力を高め、単に実行しない方がよい場合があります。例えば:
def within_required_range?
return if ([ a, b, c, d ].find(&:blank?))
# ...
end
これは、変数a
からd
のいずれかが空白の場合、救済されます。これには、nil、空の配列または文字列などが含まれます。
少し複雑な状況の代替策は、最初に依存属性の検証を実行するヘルパーメソッドを作成することです。次に、:calculations_ok?検証は条件付きで実行されます。
validates :attribute1, :presence => true
validates :attribute2, :presence => true
...
validates :attribute7, :presence => true
validate :calculations_ok?, :unless => Proc.new { |a| a.dependent_attributes_valid? }
def dependent_attributes_valid?
[:attribute1, ..., :attribute7].each do |field|
self.class.validators_on(field).each { |v| v.validate(self) }
return false if self.errors.messages[field].present?
end
return true
end
依存属性の検証が非常に複雑だったため、プロジェクトに対してこのようなものを作成する必要がありました。私の同等の:calculations_ok?依存属性が適切に検証されなかった場合、例外がスローされます。
利点:
警告:
チェックアウト http://railscasts.com/episodes/211-validations-in-Rails-
カスタムバリデーターを実装した後、あなたは単に行います
validates :attribute1, :calculations_ok => true
これで問題が解決するはずです。
James Hソリューションが私にとって最も理にかなっています。ただし、考慮すべきもう1つの点は、依存する検証に条件がある場合は、それをチェックして、dependent_attributes_validもチェックする必要があるということです。仕事に電話する。
すなわち。
validates :attribute1, presence: true
validates :attribute1, uniqueness: true, if: :attribute1?
validates :attribute1, numericality: true, unless: Proc.new {|r| r.attribute1.index("@") }
validates :attribute2, presence: true
...
validates :attribute7, presence: true
validate :calculations_ok?, unless: Proc.new { |a| a.dependent_attributes_valid? }
def dependent_attributes_valid?
[:attribute1, ..., :attribute7].each do |field|
self.class.validators_on(field).each do |v|
# Surely there is a better way with rails?
existing_error = v.attributes.select{|a| self.errors[a].present? }.present?
if_condition = v.options[:if]
validation_if_condition_passes = if_condition.blank?
validation_if_condition_passes ||= if_condition.class == Proc ? if_condition.call(self) : !!self.send(if_condition)
unless_condition = v.options[:unless]
validation_unless_condition_passes = unless_condition.blank?
validation_unless_condition_passes ||= unless_condition.class == Proc ? unless_condition.call(self) : !!self.send(unless_condition)
if !existing_error and validation_if_condition_passes and validation_unless_condition_passes
v.validate(self)
end
end
return false if self.errors.messages[field].present?
end
return true
end
かなり前にこの問題が発生したことを覚えていますが、検証の順序を設定できるかどうかはまだ不明で、検証がエラーを返した場合に実行チェーンが停止します。
Railsがこのオプションを提供しているとは思いません。これは理にかなっています。レコードのすべてのエラーを表示します(無効な入力、検証が原因で失敗した後に発生するエラーを含む)。 )。
考えられる1つのアプローチは、検証する入力が存在する場合にのみ検証することです。
def within_required_range?
return unless [:attribute1, attribute2, ..].all?(&:present?)
# check the calculations and return true or false here
end
Rails慣用的な検証オプション:
validates :attribute1, :presence => true
validates :attribute2, :presence => true
# and so on through the attributes
validate :calculations_ok?, if: :attributes_present?
private
def attributes_present?
[:attribute1, attribute2, ..].all?(&:present?)
end
def calculations_ok?
errors[:base] << "Not within required range" unless within_required_range?
end
def within_required_range?
# check the calculations and return true or false here
end