Rails model.valid? flushing custom errors and falsely returning true

前端 未结 5 746
一向
一向 2021-01-07 16:56

I am trying to add a custom error to an instance of my User model, but when I call valid? it is wiping the custom errors and returning true.

[99] pry(main)&g         


        
相关标签:
5条回答
  • 2021-01-07 17:04

    A clean way to achieve your needs is contexts, but if you want a quick fix, do:

    #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?
    
    0 讨论(0)
  • 2021-01-07 17:07

    create new concerns

    app/models/concerns/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
    

    include the model

    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"]}
    
    0 讨论(0)
  • 2021-01-07 17:17

    This is not a replacement for using the provided validations/framework. However, in some exceptional scenarios, you want to gracefully return an errd model. I would only use this when other alternatives aren't possible. One of the few scenarios I have had to use this approach is inside of a service object creating a model where some portion of the create fails (like resolving a dependent entity). It doesn't make sense for our domain model to be responsible for this type of validation, so we don't store it there (which is why the service object is doing the creation in the first place). However for simplicity of the API design it can be convenient to hang a domain error like 'associated entity foo not found' and return via the normal rails 422/unprocessible entity flow.

    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
    

    Use as some_instance.extend(ModelWithErrors.new(:name, "is gibberish", :height, "is nonsense")

    0 讨论(0)
  • 2021-01-07 17:20

    In ActiveModel, valid? is defined as following:

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

    So existing errors are cleared is expected. You have to put all your custom validations into some validate callbacks. Like this:

    validate :check_status
    
    def check_status
      errors.add(:status, "must be YES or NO") unless ['YES', 'NO'].include?(status)
    end
    
    0 讨论(0)
  • 2021-01-07 17:25

    If you want to force your model to show the errors you could do something as dirty as this:

    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"]} 
    

    The define_singleton_method method can override the .valid? behaviour.

    0 讨论(0)
提交回复
热议问题