Rails枚举验证不起作用但引发ArgumentError

时间:2016-05-12 05:20:03

标签: ruby-on-rails enums

创建了一个帖子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

运行上述测试用例时,我收到Rails issues

中描述的ArgumentError异常

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

此外,我知道在here的导轨下实际发生了什么,我不想在每个代码块中手动解救这种异常枚举类型值!

对此有何建议?

3 个答案:

答案 0 :(得分:5)

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

这个解决方案不是很优雅,但它确实有用。

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

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-team选择提出ArgumentError而不是验证错误是正确的,因为我们可以完全控制用户可以从单选按钮组中选择哪些选项,或者可以选择{ {1}}字段,因此如果程序员碰巧添加了一个新的单选按钮,其值为拼写错误,那么最好引发错误,因为它是应用程序错误,而不是用户错误。

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

答案 1 :(得分:3)

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

答案 2 :(得分:0)

想介绍另一种解决方案。

<div class="product-page">
  <div class="form">
    <label>Product 1
                <input name="product 1" value="0" id="product_1" type="number" size="25" min="0" required>
            </label>
    <label>Product 2
                <input name="product 2" value="0" id="product_2" type="number" size="25" min="0" required>
            </label>
    <label>Product 3
                <input name="product 3" value="0" id="product_3" type="number" size="25" min="0" required>
            </label>
    <label>Product 4
                <input name="product 4" value="0" id="product_4" type="number" size="25" min="0" required>
            </label>
    <button onclick="earnings()">Salesperson's Earnings</button>
    <p id="click_here"></p>
  </div>
</div>

这将避免控制器中的 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 。在我的 Rails 6 应用程序上运行良好。