【问题标题】:Rails enum validation not working but raise ArgumentErrorRails 枚举验证不起作用但引发 ArgumentError
【发布时间】:2016-09-07 18:07:42
【问题描述】:

创建了一个线程here,但它并没有解决我的问题。

我的代码是:

course.rb

class Course < ApplicationRecord
  COURSE_TYPES = %i( trial limited unlimited )
  enum course_type: COURSE_TYPES
  validates_inclusion_of :course_type, in: COURSE_TYPES
end

courses_controller.rb

class CoursesController < ApiController
  def create
    course = Course.new(course_params) # <-- Exception here
    if course.save # <-- But I expect the process can go here
      render json: course, status: :ok
    else
      render json: {error: 'Failed to create course'}, status: :unprocessable_entity
    end
  end

  private    
    def course_params
      params.require(:course).permit(:course_type)
    end
end

我的测试用例:

courses_controller_spec.rb

describe '#create' do
  context 'when invalid course type' do
    let(:params) { { course_type: 'english' } }
    before { post :create, params: { course: params } }

    it 'returns 422' do
      expect(response.status).to eq(422)
    end
  end
end

在运行上述测试用例时,我遇到了 ArgumentError 异常,该异常在 Rails issues 中进行了描述

所以我希望如果我将无效的course_type 设置为枚举,它将在验证阶段失败而不是引发异常

此外,我知道在here 的rails 中真正发生了什么,并且我不想在每个分配枚举类型值的代码块中手动拯救这种异常!

对此有何建议?

【问题讨论】:

    标签: ruby-on-rails enums


    【解决方案1】:

    想介绍另一种解决方案。

    class Course < ApplicationRecord
      COURSE_TYPES = %i[ trial limited unlimited ]
      enum course_type: COURSE_TYPES
    
      validate do
        if @not_valid_course_type
          errors.add(:course_type, "Not valid course type, please select from the list: #{COURSE_TYPES}")
        end
      end
    
      def course_type=(value)
        if !COURSE_TYPES.include?(value.to_sym)
          @not_valid_course_type = true
        else
          super value
        end
      end
    end
    

    这将避免控制器中的ArgumentError。在我的 Rails 6 应用程序上运行良好。

    【讨论】:

      【解决方案2】:

      我找到了解决方案。我自己在 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
      

      用法:

      contact = Contact.new(kind: 'foo')
      contact.valid? #=> false
      contact.errors.full_messages #=> ["Kind is not included in the list"]
      

      【讨论】:

      • 这是一个非常好的解决方案。这就是我最终所做的。我唯一改变的是使用defined_enums.fetch(attribute.to_s) 而不是public_send(attribute.to_s.pluralize)
      【解决方案3】:

      已更新以支持 .valid? 进行幂等验证。

      这个解决方案不是很优雅,但是很有效。

      我们在 API 应用程序中遇到了这个问题。我们不喜欢 rescue每次需要在任何控制器或操作中使用此错误时的想法。所以我们在模型端rescued 如下:

      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
      

      可以说,Rails 团队选择引发 ArgumentError 而不是验证错误是正确的,因为我们可以完全控制用户可以从单选按钮组中选择哪些选项,或者可以选择 select字段,所以如果程序员碰巧添加了一个新的单选按钮,其值有错字,那么最好提出一个错误,因为这是一个应用程序错误,而不是用户错误。

      但是,对于 API,这将不起作用,因为我们无法再控制将哪些值发送到服务器。

      【讨论】:

      • 嘿,这是个好点,但不会起作用,如果你运行 :valid?,errors 将首先被清除,看看这个stackoverflow.com/questions/5159612/…
      • 是的,如果您在设置course_type 的值之前运行.valid?,首先确实没有错误,因为还没有值,除非您有:presence 验证或其他验证课程类型。您需要进行存在验证,以便一开始就已经存在错误。这与其他模型验证一致,即 :inclusion,就像我上面的回答一样。请注意,存在和包含是相互独立的
      • 男人!当然,问题是您的解决方案不会像您预期的那样将错误消息添加到error
      • 嗯,这很奇怪。这是我们在项目中使用的确切代码,所以我很肯定错误已设置。尽管我们在那种情况下并没有真正使用过.valid?,所以也许这就是原因。我会在回家后检查以验证这一点。
      • 你是对的。 .valid? 清除所有错误,然后运行验证。感谢那。今天学到了新东西。我更新了我的答案。我测试了这个工作。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-10-25
      • 2016-10-12
      • 1970-01-01
      • 1970-01-01
      • 2018-07-08
      • 2015-06-29
      • 1970-01-01
      相关资源
      最近更新 更多