Hie*_*ham 4 enums ruby-on-rails
我的代码是:
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我的测试用例:
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
Run Code Online (Sandbox Code Playgroud)
运行上面的测试用例时,我得到了Rails问题中ArgumentError描述的异常
所以我希望如果我将course_type枚举设置为无效,它将在验证阶段失败,而不是引发异常.
另外,我知道在这里的 rails中挂钩下真正发生了什么,我不想在分配枚举类型值的每个代码块中手动挽救这种异常!
有什么建议吗?
Ali*_*ndr 10
我找到了解决办法。我自己在 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
Run Code Online (Sandbox Code Playgroud)
# 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
Run Code Online (Sandbox Code Playgroud)
# 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
Run Code Online (Sandbox Code Playgroud)
用法:
contact = Contact.new(kind: 'foo')
contact.valid? #=> false
contact.errors.full_messages #=> ["Kind is not included in the list"]
Run Code Online (Sandbox Code Playgroud)
小智 9
想介绍另一种解决方案。
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
Run Code Online (Sandbox Code Playgroud)
这将在控制器中避免ArgumentError。在我的 Rails 6 应用程序上运行良好。
更新以支持.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
Run Code Online (Sandbox Code Playgroud)
可以说,rails-team选择提升ArgumentError而不是验证错误是正确的,因为我们可以完全控制用户可以从单选按钮组中选择哪些选项,或者可以选择一个select字段,所以如果程序员碰巧添加一个新的单选按钮,其值有一个拼写错误,然后提出错误是好的,因为它是一个应用程序错误,而不是用户错误.
但是,对于API,这将不起作用,因为我们不再对将哪些值发送到服务器有任何控制.