Подтвердить что ты не робот

Rails model.valid? очистка пользовательских ошибок и ложное возвращение

Я пытаюсь добавить пользовательскую ошибку к экземпляру моей модели User, но когда я позвоню вам? он очищает пользовательские ошибки и возвращает true.

[99] pry(main)> u.email = "[email protected]"
"[email protected]"

[100] pry(main)> u.status = 1
1

[101] pry(main)> u.valid?
true

[102] pry(main)> u.errors.add(:status, "must be YES or NO")
[
    [0] "must be YES or NO"
]

[103] pry(main)> u.errors
#<ActiveModel::Errors:[...]@messages={:status=>["must be YES or NO"]}>

[104] pry(main)> u.valid?
true

[105] pry(main)> u.errors
#<ActiveModel::Errors:[...]@messages={}>

Если я использую метод validate из модели, то он работает, но эта конкретная проверка добавляется из другого метода (который требует передачи параметров):

User

def do_something_with(arg1, arg2)
  errors.add(:field, "etc") if arg1 != arg2
end

Из-за вышеизложенного, user.valid? возвращает true, даже когда эта ошибка добавляется в экземпляр.

4b9b3361

Ответ 1

В ActiveModel valid? определяется следующим образом:

def valid?(context = nil)
  current_context, self.validation_context = validation_context, context
  errors.clear
  run_validations!
ensure
  self.validation_context = current_context
end

Ожидается, что существующие ошибки будут очищены. Вы должны поместить все свои пользовательские проверки в некоторые обратные вызовы validate. Вот так:

validate :check_status

def check_status
  errors.add(:status, "must be YES or NO") unless ['YES', 'NO'].include?(status)
end

Ответ 2

Если вы хотите заставить свою модель показать ошибки, вы можете сделать что-то как грязно, как это:

your_object = YourModel.new 
your_object.add(:your_field, "your message")
your_object.define_singleton_method(:valid?) { false }
# later on...
your_object.valid?
# => false
your_object.errors
# => {:your_field =>["your message"]} 

Метод define_singleton_method может переопределить поведение .valid?.

Ответ 3

Это не замена для использования предоставленных проверок/структуры. Однако в некоторых исключительных сценариях вы хотите изящно возвратить модель ошибки. Я бы использовал это только тогда, когда другие альтернативы невозможны. Один из немногих сценариев, в которых мне пришлось использовать этот подход, находится внутри сервисного объекта, создающего модель, в которой происходит сбой в некоторой части процесса создания (например, при разрешении зависимой сущности). Не имеет смысла, чтобы наша модель предметной области отвечала за этот тип проверки, поэтому мы не храним ее там (именно поэтому объект службы выполняет создание в первую очередь). Однако для простоты конструкции API может быть удобно повесить ошибку домена, такую как "связанный объект не найден", и вернуть через обычный рельс 422/поток непроцессируемого объекта.

class ModelWithErrors
  def self.new(*errors)
    Module.new do
      define_method(:valid?) { false }
      define_method(:invalid?) { true }
      define_method(:errors) do
        errors.each_slice(2).with_object(ActiveModel::Errors.new(self)) do |(name, message), errs|
          errs.add(name, message)
        end
      end
    end
  end
end

Использовать как some_instance.extend(ModelWithErrors.new(:name, "is gibberish", :height, "is nonsense")

Ответ 4

создавать новые проблемы

приложение/модели/проблемы/static_error.rb

module StaticError
  extend ActiveSupport::Concern

  included do
    validate :check_static_errors
  end

  def add_static_error(*args)
    @static_errors = [] if @static_errors.nil?
    @static_errors << args

    true
  end

  def clear_static_error
    @static_errors = nil
  end

  private

  def check_static_errors
    @static_errors&.each do |error|
      errors.add(*error)
    end
  end
end

включить модель

class Model < ApplicationRecord
  include StaticError
end
model = Model.new
model.add_static_error(:base, "STATIC ERROR")
model.valid? #=> false
model.errors.messages #=> {:base=>["STATIC ERROR"]}

Ответ 5

Чистый способ удовлетворить ваши потребности - это контексты, но если вам нужно быстрое решение, выполните:

#in your model
attr_accessor :with_foo_validation
validate :foo_validation, if: :with_foo_validation

def foo_validation
  #code 
end

#where you need it
your_object.with_foo_validation = true
your_object.valid?