将ActiveRecordvalidation错误转换为API耗材错误
我正在Rails 4中编写一个非常标准的CRUD RESTful API。虽然我在error handling方面做得不够。
想象一下,我有以下型号:
class Book < ActiveRecord::Base validates :title, presence: true end
如果我尝试创建没有标题的图书对象,我将收到以下错误:
{ "title": [ "can't be blank" ] }
ActiveRecordvalidation旨在与Forms一起使用。 理想情况下,我希望将每个人类可读validation错误与API使用者可以使用的常量相匹配。 所以类似于:
{ "title": [ "can't be blank" ], "error_code": "TITLE_ERROR" }
这可以用于显示面向用户的错误( “标题不能为空” ),也可以在其他代码中使用( if response.error_code === TITLE_ERROR
…)。 在Rails中是否有任何工具?
编辑:这是Rails 2天非常相似的问题 。
在error_codes.yml上定义标准API错误,包括status_code
, title
, details
和内部code
,然后您可以使用它们提供有关API文档错误的更多信息。
这是一个基本的例子:
api: invalid_resource: code: '1' status: '400' title: 'Bad Request' not_found: code: '2' status: '404' title: 'Not Found' details: 'Resource not found.'
在config / initializers / api_errors.rb上将 YAML文件加载到常量中。
API_ERRORS = YAML.load_file(Rails.root.join('doc','error-codes.yml'))['api']
在app / controllers / concerns / error_handling.rb上定义一个可重用的方法来呈现JSON格式的API错误:
module ErrorHandling def respond_with_error(error, invalid_resource = nil) error = API_ERRORS[error] error['details'] = invalid_resource.errors.full_messages if invalid_resource render json: error, status: error['status'] end end
在您的API基础控制器上包含关注点,因此它可以在从它inheritance的所有控制器上使用:
include ErrorHandling
然后,您就可以在任何控制器上使用您的方法:
respond_with_error('not_found') # For standard API errors respond_with_error('invalid_resource', @user) # For invalid resources
例如,在您的用户控制器上,您可能具有以下内容:
def create if @user.save(your_api_params) # Do whatever your API needs to do else respond_with_error('invalid_resource', @user) end end
您的API将输出的错误如下所示:
# For invalid resources { "code": "1", "status": "400", "title": "Bad Request", "details": [ "Email format is incorrect" ] } # For standard API errors { "code": "2", "status": "404", "title": "Not Found", "details": "Route not found." }
随着API的增长,您将能够轻松地在YAML文件中添加新的错误代码,并使用此方法避免重复,并使您的错误代码在API中保持一致。
你的create方法看起来应该是这样的:
def create book = Book.new(book_params) if user.save render json: book, status: 201 else render json: { errors: book.errors, error_code: "TITLE_ERROR" }, status: 422 end end
这将返回看起来像你问的json,除了“title”和“error_code”将嵌套在“错误”中。 我希望不要处理大问题。
您只有两种方法可以实现此目的:要么为validation程序编写代码(在validation期间将测试错误的组件),要么编写渲染器。
我假设您知道如何编写渲染器,因为@ baron816的答案是建议的,并做一些DRY以某种方式推广它。
让我引导您完成validation器技术:
1-让我们为你的错误代码创建一个存储空间,我称之为custom_error_codes
,我假设你可以一次设置多个错误代码,所以我将使用一个Array
(否则你会改变它)。
创建模型问题
module ErrorCodesConcern extend ActiveSupport::Concern included do # storage for the error codes attr_reader :custom_error_codes # reset error codes storage when validation process starts before_validation :clear_error_codes end # default value so the variable is not empty when accessed improperly def custom_error_codes @custom_error_codes ||= [] end private def clear_error_codes @custom_error_codes = [] end end
然后将关注点添加到模型中
class MyModel < ActiveRecord::Base include ErrorCodesConcern ... end
2-让我们破解validation器添加错误代码的标记。 首先我们需要查看validation器源代码,它们位于(activemodel-gem-path)/ lib / active_model / validations /中 。
在app目录中创建一个validators目录,然后创建以下validation器
class CustomPresenceValidator < ActiveModel::Validations::PresenceValidator # this method is copied from the original validator def validate_each(record, attr_name, value) if value.blank? record.errors.add(attr_name, :blank, options) # Those lines are our customization where we add the error code to the model error_code = "#{attr_name.upcase}_ERROR" record.custom_error_codes << error_code unless record.custom_error_codes.include? error_code end end end
然后在我们的模型中使用我们的自定义validation器
class Book < ActiveRecord::Base validates :title, custom_presence: true end
3-因此,您必须修改代码正在使用的所有railsvalidation器并创建渲染器(请参阅@ baron816的答案)并使用模型的custom_error_codes
值进行响应。
看起来好像你没有考虑多个validation错误。
在您的示例中, Book
模型只有一个validation,但其他模型可能有更多validation。
我的答案包含一个解决多个validation的第一个解决方案和另一个仅使用模型上找到的第一个validation错误的解决方案
解决方案1 – 处理多个validation
在ApplicationController中添加它
# Handle validation errors rescue_from ActiveRecord::RecordInvalid do |exception| messages = exception.record.errors.messages messages[:error_codes] = messages.map {|k,v| k.to_s.upcase << "_ERROR" } render json: messages, status: 422 end
请注意,在这种情况下, error_codes
是一个允许多个错误代码的数组。 例如:
{ "title": [ "can't be blank" ], "author": [ "can't be blank" ], "error_codes": ["TITLE_ERROR", "AUTHOR_ERROR"] }
解决方案2 - 仅处理第一个validation错误
如果您确实只想保留一个validation错误,请改用它
# Handle validation errors rescue_from ActiveRecord::RecordInvalid do |exception| key = exception.record.errors.messages.keys[0] msg = exception.record.errors.messages[key] render json: { key => msg, :error_code => key.to_s.upcase << "_ERROR" }, status: 422 end
这会给你一个像
{ "title": [ "can't be blank" ], "error_code": "TITLE_ERROR" }
即使您有多个错误
试试这个:
book = Book.new(book_params) if user.save render json: book, status: 201 else render json: { errors: book.errors, error_codes: book.errors.keys.map { |f| f.upcase + "_ERROR" } }, status: 422 end
error_codes
将返回多个错误代码。