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のラッパーレイヤーを追加する新しいものでオーバーライドします。#presentこれは、Grapeのヘルパー(エラーをうまくカバーしない)やラックミドルウェアソリューション(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

そのためにミドルウェア層を使用できます。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

今日の日付の時点で、Grapeでこれを行う正しい方法は次のとおりです。

    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 に答える
1

私は@Neil-Slaterのソリューションを使用していますが、他の人が役立つと思った追加の変更が1つあります。

一般的rescue_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

-ラック環境にアクセスする適切な方法がわからなかったため、nil値として渡しています(エラーハンドラーは値を使用しないため、問題ありません)。

このアプローチをさらに拡張して、応答コードの処理をさらに改善できると思います。私にとってトリッキーな部分はRack::Response、フォーマットされたエラーメッセージを渡すことができるオブジェクトが必要だと考えていたことです。

于 2013-12-05T23:07:13.600 に答える