web-dev-qa-db-ja.com

Rails検証の順序を制御する

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が空白の属性で計算しようとするとエラーをスローすることを意味します。

では、必要なすべての属性の存在を最初に確認するにはどうすればよいですか?

50
David Tuite

attributesハッシュ自体の順序に依存する可能性があるため、これらの検証が実行される順序が保証されているかどうかはわかりません。必要なデータの一部が欠落している場合は、validateメソッドの回復力を高め、単に実行しない方がよい場合があります。例えば:

def within_required_range?
  return if ([ a, b, c, d ].find(&:blank?))

  # ...
end

これは、変数aからdのいずれかが空白の場合、救済されます。これには、nil、空の配列または文字列などが含まれます。

19
tadman

少し複雑な状況の代替策は、最初に依存属性の検証を実行するヘルパーメソッドを作成することです。次に、: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?依存属性が適切に検証されなかった場合、例外がスローされます。

利点:

  • 特に検証が複雑な場合は、比較的乾燥
  • エラー配列がマクロ検証ではなく、失敗した正しい検証を報告することを保証します
  • 後で追加する依存属性に追加の検証を自動的に含めます

警告:

  • すべての検証を2回実行する可能性があります
  • すべての検証を依存属性で実行したくない場合があります
9
James H

チェックアウト http://railscasts.com/episodes/211-validations-in-Rails-

カスタムバリデーターを実装した後、あなたは単に行います

validates :attribute1, :calculations_ok => true

これで問題が解決するはずです。

2
David Sulc

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
1
Jason Kotchoff

かなり前にこの問題が発生したことを覚えていますが、検証の順序を設定できるかどうかはまだ不明で、検証がエラーを返した場合に実行チェーンが停止します。

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
0
Arta