将ActiveRecord验证错误转换为API耗材错误

时间:2016-10-25 21:56:54

标签: ruby-on-rails rest api activerecord

我正在Rails 4中编写一个非常标准的CRUD RESTful API。虽然我的错误处理很简单。

想象一下,我有以下模型:

class Book < ActiveRecord::Base
  validates :title, presence: true
end

如果我尝试创建没有标题的图书对象,我将收到以下错误:

{
  "title": [
    "can't be blank"
  ]
}

ActiveRecord验证旨在与Forms一起使用。理想情况下,我希望将每个人类可读验证错误与API使用者可以使用的常量相匹配。如下所示:

{
  "title": [
    "can't be blank"
  ],
  "error_code": "TITLE_ERROR"
}

这既可用于显示面向用户的错误(“标题不能为空白”),也可用于其他代码(if response.error_code === TITLE_ERROR ...)。在Rails中是否有任何工具?

编辑:这是very similar question from Rails 2 days

5 个答案:

答案 0 :(得分:9)

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基础控制器上包含关注点,以便它可以从继承它的所有控制器上获得:

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中保持一致。

答案 1 :(得分:1)

你的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”嵌套在“错误”中。我希望不要处理大问题。

答案 2 :(得分:1)

您只有两种方法可以实现此目的:要么为验证程序编写代码(在验证期间将测试错误的组件),要么编写渲染器。

我假设您知道如何编写渲染器,因为@ baron816的回答是建议的,并且做了一些DRY以以某种方式概括它。

让我引导您完成验证器技术:

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 - 让黑客验证程序添加错误代码的标记。首先,我们需要查看验证器源代码,它们位于(activemodel-gem-path)/ lib / active_model / validations / 中。

app 目录中创建验证器目录,然后创建以下验证器

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

然后在我们的模型中使用我们的自定义验证器

class Book < ActiveRecord::Base
  validates :title, custom_presence: true
end

3 - 因此,您必须修改代码正在使用的所有rails验证器并创建渲染器(请参阅@ baron816&#39; s答案)并使用模型{{1进行响应价值。

答案 3 :(得分:1)

看起来您似乎没有考虑多个验证错误。

在您的示例中,Book模型只有一个验证,但其他模型可能有更多验证。

我的回答包含第一个解决多个验证的解决方案和另一个仅使用模型上找到的第一个验证错误的解决方案

解决方案1 ​​ - 处理多个验证

在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 - 仅处理第一个验证错误

如果您确实只想保留一个验证错误,请改用

# 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"
}

即使您有多个错误

答案 4 :(得分:1)

试试这个:

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