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, даже когда эта ошибка добавляется в экземпляр.
Ответы
Ответ 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?