Rails enum validation not working but raise ArgumentError

后端 未结 2 1804
盖世英雄少女心
盖世英雄少女心 2021-01-06 00:24

A thread was created here, but it doesn\'t solve my problem.

My code is:

course.rb

class Course < ApplicationRecord
  COU         


        
相关标签:
2条回答
  • 2021-01-06 00:39

    UPDATED to support .valid? to have idempotent validations.

    This solution isn't really elegant, but it works.

    We had this problem in an API application. We do not like the idea of rescueing this error every time it is needed to be used in any controller or action. So we rescued it in the model-side as follows:

    class Course < ApplicationRecord
      validate :course_type_should_be_valid
    
      def course_type=(value)
        super value
        @course_type_backup = nil
      rescue ArgumentError => exception
        error_message = 'is not a valid course_type'
        if exception.message.include? error_message
          @course_type_backup = value
          self[:course_type] = nil
        else
          raise
        end
      end
    
      private
    
      def course_type_should_be_valid
        if @course_type_backup
          self.course_type ||= @course_type_backup
          error_message = 'is not a valid course_type'
          errors.add(:course_type, error_message)
        end
      end
    end
    

    Arguably, the rails-team's choice of raising ArgumentError instead of validation error is correct in the sense that we have full control over what options a user can select from a radio buttons group, or can select over a select field, so if a programmer happens to add a new radio button that has a typo for its value, then it is good to raise an error as it is an application error, and not a user error.

    However, for APIs, this will not work because we do not have any control anymore on what values get sent to the server.

    0 讨论(0)
  • 2021-01-06 00:47

    I've found a solution. Tested by myself in Rails 6.

    # app/models/contact.rb
    class Contact < ApplicationRecord
      include LiberalEnum
    
      enum kind: {
        phone: 'phone', skype: 'skype', whatsapp: 'whatsapp'
      }
    
      liberal_enum :kind
    
      validates :kind, presence: true, inclusion: { in: kinds.values }
    end
    
    # app/models/concerns/liberal_enum.rb
    module LiberalEnum
      extend ActiveSupport::Concern
    
      class_methods do
        def liberal_enum(attribute)
          decorate_attribute_type(attribute, :enum) do |subtype|
            LiberalEnumType.new(attribute, public_send(attribute.to_s.pluralize), subtype)
          end
        end
      end
    end
    
    # app/types/liberal_enum_type.rb
    class LiberalEnumType < ActiveRecord::Enum::EnumType
      # suppress <ArgumentError>
      # returns a value to be able to use +inclusion+ validation
      def assert_valid_value(value)
        value
      end
    end
    

    Usage:

    contact = Contact.new(kind: 'foo')
    contact.valid? #=> false
    contact.errors.full_messages #=> ["Kind is not included in the list"]
    
    0 讨论(0)
提交回复
热议问题