PIYO - Tech & Life -

Grapeを使ったAPI実装でのネストやパラメータ

API gem Grape Rails

Grapeをもう少し使ってみました。関連記事は↓

RailsのGrapeとJbuilderでAPI開発 - ぴよログ

基本形

# app/api/api.rb
resource :items do
  get '/', jbuilder:'items' do
    @items = Item.all
  end
end

パラメータを受け取る

全てのItemではなく、idに該当するItemだけが欲しいとき。

resource :items do
  get '/:id', jbuilder:'item' do
    @item = Item.find_by_id(params[:id])
  end
end

/api/items/1というエンドポイントでid=1のItemが得られます。

ネストする

  • Item
  • Entry

というモデルがあって、Item has many Entriesな関連を持っている場合はリソースをネストさせることが多いと思います。Railsのルーティングとかでもよくやるアレです。Grapeでも似たような書き方ができます。

resource :items do
  route_param :item_id do
    resource :entries do
      get '/', jbuilder:'entries' do
        @item = Item.find_by_id(params[:item_id])
        @entries = @item.entries if @item
      end

      get '/:id', jbuilder:'entry' do
        @item = Item.find_by_id(params[:item_id])
        @entry = @item.entries.find_by_id(params[:id]) if @item
      end
    end
  end
end

上のコードにより2つのAPIが定義されます。

  • /api/items/1/entriesでItem(id=1)のEntryを全て返す
  • /api/items/1/entries/1でItem(id=1)のEntryのうち(id=1)のものを返す

ネストの階層はこのようになっています。

  1. resource :items
  2. route_param :item_id
  3. resource :entries

2番めに出てくるroute_paramというのがありますが、実はこれresourceのエイリアスでGrapeの中では同じように扱われます。

公式ドキュメントによると元々はnamespaceというメソッドがあって、それに対するaliasとして以下のものが定義されているようです。

  • namespace
  • group
  • resource
  • resources
  • segment
  • route_param

読みやすくなるようコンテキストに応じて使えばよいということで、上のような場合はやはりresource(s)route_paramが適切っぽいでしょう。

API定義全体

class API < Grape::API
  format :json
  formatter :json, Grape::Formatter::Jbuilder
  default_format :json

  resource :items do
    get '/', jbuilder:'items' do
      @items = Item.all
    end

    params do
      requires :id, type: Integer, desc: "Item ID"
    end
    get '/:id', jbuilder:'item' do
      @item = Item.find_by_id(params[:id])
    end

    params do
      requires :item_id, type: Integer, desc: "Item ID"
    end
    route_param :item_id do
      resource :entries do
        get '/', jbuilder:'entries' do
          @item = Item.find_by_id(params[:item_id])
          @entries = @item.entries if @item
        end

        params do
          requires :id, type: Integer, desc: "Entry ID"
        end
        get '/:id', jbuilder:'entry' do
          @item = Item.find_by_id(params[:item_id])
          @entry = @item.entries.find_by_id(params[:id]) if @item
        end
      end
    end
  end

end