将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_codetitledetails和内部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将返回多个错误代码。