10

我有一个Grape::API作为机架服务的小型原型子类,Grape::Entity用于展示我的应用程序的内部对象。

我喜欢Grape::EntityDSL,但我很难找出我应该如何超越默认 JSON 表示,这对于我们的目的来说太轻量级了。我被要求以“jsend 或类似”格式生成输出:http: //labs.omniti.com/labs/jsend

我完全不确定什么性质的变化最符合 Grape 框架(我想要一条阻力最小的路径)。我是否应该创建一个自定义的 Grape 格式化程序(我不知道该怎么做),新的机架中间件(我这样做是为了通过 SysLog 记录 API 输入/输出 - 但格式化似乎很糟糕,因为我需要解析正文从 JSON 返回以添加容器级别),还是从Grape::Entity例如 RABL 更改?

示例代码(“app.rb”)

require "grape"
require "grape-entity"

class Thing
  def initialize llama_name
    @llama_name = llama_name
  end
  attr_reader :llama_name
end

class ThingPresenter < Grape::Entity
  expose :llama_name
end

class MainService < Grape::API
  prefix      'api'
  version     'v2'
  format      :json
  rescue_from :all

  resource :thing do
    get do
      thing = Thing.new 'Henry'
      present thing, :with => ThingPresenter
    end
  end
end

备份文件(“config.ru”)

require File.join(File.dirname(__FILE__), "app")
run MainService

我启动它:

rackup -p 8090

并称之为:

curl http://127.0.0.1:8090/api/v2/thing
{"llama_name":"Henry"}

我想看到的:

curl http://127.0.0.1:8090/api/v2/thing
{"status":"success","data":{"llama_name":"Henry"}}

显然我可以做类似的事情

  resource :thing do
    get do
      thing = Thing.new 'Henry'
      { :status => "success", :data => present( thing, :with => ThingPresenter ) }
    end
  end

在每条路线上 - 但这似乎不是很干燥。当这个 API 变得更大并由整个团队维护时,我正在寻找更清洁的东西,并且更不容易出现剪切和粘贴错误


奇怪的是,当我尝试{ :status => "success", :data => present( thing, :with => ThingPresenter ) }使用时grape 0.3.2,我无法让它工作。API 只返回了来自的值present——这里发生的事情比我最初想象的要多。

4

5 回答 5

15

通过阅读 Grape 文档、谷歌搜索和阅读 github 上的一些拉取请求,这就是我最终得到的结果。基本上,在声明:json格式之后(以获取所有其他默认的好东西),我用添加了 jsend 包装层的新格式来覆盖输出格式。与尝试包装 Grape 的#present帮助程序(不能很好地覆盖错误)或机架中间件解决方案(需要反序列化和重新序列化 JSON,并且需要大量额外的代码来覆盖错误)相比,这对代码来说要干净得多.

require "grape"
require "grape-entity"
require "json"

module JSendSuccessFormatter
  def self.call object, env
    { :status => 'success', :data => object }.to_json
  end
end

module JSendErrorFormatter
  def self.call message, backtrace, options, env
    # This uses convention that a error! with a Hash param is a jsend "fail", otherwise we present an "error"
    if message.is_a?(Hash)
      { :status => 'fail', :data => message }.to_json
    else
      { :status => 'error', :message => message }.to_json
    end
  end
end

class Thing
  def initialize llama_name
    @llama_name = llama_name
  end
  attr_reader :llama_name
end

class ThingPresenter < Grape::Entity
  expose :llama_name
end

class MainService < Grape::API
  prefix      'api'
  version     'v2'
  format      :json
  rescue_from :all

  formatter :json, JSendSuccessFormatter
  error_formatter :json, JSendErrorFormatter

  resource :thing do
    get do
      thing = Thing.new 'Henry'
      present thing, :with => ThingPresenter
    end
  end

  resource :borked do
    get do
      error! "You broke it! Yes, you!", 403
    end
  end
end
于 2013-03-19T14:55:04.043 回答
2

我相信这可以实现您在使用时的目标grape

require "grape"
require "grape-entity"

class Thing
  def initialize llama_name
    @llama_name = llama_name
  end
  attr_reader :llama_name
end

class ThingPresenter < Grape::Entity
  expose :llama_name
end

class MainService < Grape::API
  prefix      'api'
  version     'v2'
  format      :json
  rescue_from :all

  resource :thing do
    get do
      thing = Thing.new 'Henry'
      present :status, 'success'
      present :data, thing, :with => ThingPresenter
    end
  end
end
于 2014-05-09T20:24:02.083 回答
1

我正在使用@Neil-Slater 的解决方案和我认为其他人可能会觉得有用的一项额外修改。

只有 arescue_from :all常见 404 错误的结果返回为403 Forbidden. 此外,当它应该是“失败”时,状态是“错误”。为了解决这些问题,我为 RecordNotFound 添​​加了一个救援处理程序:

rescue_from ActiveRecord::RecordNotFound do |e|
  Rails.logger.info e.message
  error = JSendErrorFormatter.call({message: e.message}, e.backtrace, {}, nil)
  Rack::Response.new(error, 404,
                     { "Content-type" => "text/error" }).finish
end

注意- 我无法找出访问 rack env 的正确方法,因此您可以看到我将它作为 nil 值传入(这没关系,因为错误处理程序不使用该值)。

我想您可以进一步扩展此方法以进一步完善响应代码处理。对我来说,棘手的部分是我需要一个Rack::Response可以将格式化的错误消息传递到的对象。

于 2013-12-05T23:07:13.600 回答
1

您可以为此使用中间件层。Grape 有一个Middleware::Base可用于此目的的模块。我不太漂亮的实现:

class StatusAdder < Grape::Middleware::Base

  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, response = @app.call
    response_hash = JSON.parse response.body.first
    body = { :status => "success", :data => response_hash } if status == 200

    response_string = body.to_json
    headers['Content-Length'] = response_string.length.to_s
    [status, headers, [response_string]]
  end
end

MainService课堂上,您将添加一行:use ::StatusAdder

于 2013-03-19T14:01:44.490 回答
1

截至今天,我相信用葡萄做到这一点的正确方法是:

    rescue_from Grape::Exceptions::ValidationErrors do |e|
        response = 
        {
            'status' => 'fail',
            'data' => {
                'status' => e.status,
                'message' => e.message,
                'errors' => e.errors 
            }
        }
        Rack::Response.new(response.to_json, e.status)
    end
于 2013-10-01T02:09:53.297 回答