LoginSignup
45
38

More than 5 years have passed since last update.

RESTful APIにおいて一対多関係のデータ構造をGrape::Entityを使って表現する

Last updated at Posted at 2015-03-01

RubyでRESTfulなAPIサーバを作るのに役立つGrapeですが、ぜひとも一緒に使いたいのがGrape::Entityです

Grape::EntityはView的な役割を果たし、DBの値をpresentするだけで、Entityで指定したデータ構造を作ってくれ、最終的な出力整形をGrapeが行ってくれます

これで、DBからの値をmap等を駆使してHash構造を作る...そんな人生とは決別です

日本語情報が少ないのですが、コードサンプルやspecが充実しているので、実装で困ることはありませんが、一対多なデータ構造を表現する方法が乏しかったので、メモ代わりに残しておきます

  • Ruby 2.1.5
  • Grape 0.11.0
  • Grape::Entity 0.4.4

データ構造

ParentとChildrenテーブルを用意、Parent/Childというモデル名で定義しています
Parent:Child = 1:N です

※ORMにSequelを使っていますが、ActiveRecordでも同じです

001_devel_collection.rb
Sequel.migration do
  up do
    create_table :children do
      primary_key :id
      Integer :parent_id
      String  :name
      String  :birth_day
    end

    create_table :parents do
      primary_key :id
      String :name
      String :addr
    end
  end

  down do
    drop_table :children
    drop_table :parents
  end
end
app/models/child.rb
class Child < Sequel::Model
  many_to_one :parent
end
app/models/parent.rb
class Parent < Sequel::Model
  one_to_many :child
end

API実装

Grape自体のインストール等はconfig.ruについては割愛

api.rb
module API::Entities
  class Child < Grape::Entity
    expose :name, as: "name"
    # birth_day column in DB does not expose (hide)
  end

  class Parent < Grape::Entity
    expose :name,  as: "parent_name"
    expose :addr
    expose :child, as: "children", using: API::Entities::Child
  end
end

class MyAPI < Grape::API
  prefix "/api"
  format :json
  desc "Testing collection expose w/ grape-entity"
  get "/collection/:name" do
    pt = Parent.find(name: params[:name])
    present pt, with: API::Entities::Parent
  end
end

これで、下記の通りchildrenというHash keyの下に配列でchildが並びます

$ curl http://localhost:9292/api/collection/pname1
{"parent_name":"pname1","addr":"Streat Name","children":[{"name":"c1"},{"name":"c2"}]}

RSpec

curlでテストするのは忍びないので、RSpecも置いておきます

spec/spec_helper.rbは割愛しますが、このあたりのgemを使っています

require "rack/test"
require "rspec/its"
require "rspec/json_matcher"
RSpec.configuration.include RSpec::JsonMatcher
require "json"
require "database_cleaner"
spec/api_spec.rb
require "spec_helper"

describe MyAPI do
  include Rack::Test::Methods
  def app ; described_class ; end

  before {
    p1 = Parent.create(name: "pname1", addr: "Streat Name")
    p1.add_child(name: "c1")
    p1.add_child(name: "c2")
    Parent.create(name: "p2")
  }

  context "get" do
    subject { get "/api/collection/pname1" }
    let(:result) do
      {
        parent_name: "pname1",
        addr:        "Streat Name",
        children:    [
                       {name: "c1"},
                       {name: "c2"}
                     ]
      }
    end
    its(:body) { should be_json_as result }
  end
end

まとめ

実装コードが、たった2行というところが非常によろしいです

    pt = Parent.find(name: params[:name])
    present pt, with: API::Entities::Parent

また、DBにはchildren.birth_dayというカラムを作成していますが、API::Entities::Childではexposeの指定をしていないので、出力を隠すことに成功しています

DBからは取得されてしまっているので通信量では不利ですが、宣言的にコードが書けるため保守性は極めて高いのではないでしょうか

45
38
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
38