JsonWorldを利用して生成したJSON Schemaを読み込み、JsonismでAPIを呼び出す

@r7kamuraさん作のJsonWorldを利用して生成したJSON Schemaを、
同じく@r7kamuraさん作のJsonismで読み込み、APIを呼び出します

## <i class="fa fa-check-circle" style="font-size:1em;"></i> 準備
JsonWorld, Jsonismについては@r7kamuraさん本人による下記のエントリをチェック

<i class="fa fa-external-link" style="font-size:1em;"></i> [全てがJSONになる](http://r7kamura.hatenablog.com/entry/2014/06/10/023433)

## <i class="fa fa-flask" style="font-size:1em;"></i> サンプル
Railsを利用します

### <i class="fa fa-file-text-o" style="font-size:1em;"></i> 仕様
Railsのscaffoldで以下のモデルを作成しました。

|attr|type|
|:--|:--|
|People#name|String|
|People#age|Integer|

### <i class="fa fa-database" style="font-size:1em;"></i> Resourceクラス作成

~~~ruby:app/api/resources/person.rb
require 'json_world'

module Api
  module Resources
    class Person
      include JsonWorld::DSL

      title 'Person'
      property(
        :id,
        description: '人物のid',
        example: 1,
        type: Integer,
      )

      property(
        :name,
        description: '人物の名前',
        example: "tanaka",
        type: String,
      )

      property(
        :age,
        description: '人物の年齢',
        example: 34,
        type: Integer,
      )

      link(
        :list,
        description: "全ての人物の一覧を取得します。",
        path: "/api/v1/people",
        rel: "instances",
      )

      link(
        :get,
        description: "人物を取得します。",
        path: "/api/v1/people/:id",
        rel: "self",
      )

      link(
        :create,
        description: "人物を登録します。",
        path: "/api/v1/people/:id",
        method: "POST",
        rel: "self",
        parameters: {
          name: {
            description: "人物の名前",
            example: "tanaka",
            type: String,
          },
          age: {
            description: "人物の年齢",
            example: 34,
            type: Integer,
          },
        },
      )

      link(
        :update,
        description: "人物を更新します。",
        path: "/api/v1/people/:id",
        method: "PATCH",
        rel: "self",
        parameters: {
          name: {
            description: "人物の名前",
            example: "tanaka",
            type: String,
          },
          age: {
            description: "人物の年齢",
            example: 34,
            type: Integer,
          },
        },
      )

      link(
        :delete,
        description: "人物を削除します。",
        path: "/api/v1/people/:id",
        method: "DELETE",
        rel: "self",
      )

      attr_reader :id, :name, :age

      # @param [Person] person
      def initialize(person, people)
        @id = person.id
        @name = person.name
        @age = person.age
        @people = people.age
      end
    end
  end
end
~~~

### <i class="fa fa-sitemap" style="font-size:1em;"></i> Schemaクラス作成

~~~ruby:app/api/schema.rb
require 'json_world'
require "#{Rails.root}/app/api/resources/person.rb"

module Api
  class Schema
    include JsonWorld::DSL

    title 'Sample API v1 JSON Schema'
    description 'このスキーマ定義では、Sample API v1 のインターフェースをJSON Hyper Schema draft v4形式で表現しています。'

    property :people, links: true, type: Api::Resources::Person
    link href: "http://localhost:3000", rel: "self"
  end
end
~~~

### <i class="fa fa-rss" style="font-size:1em;"></i> Controller

~~~ruby:app/controllers/api/v1/people_controller.rb
require "#{Rails.root}/app/controllers/application_controller.rb"
require "#{Rails.root}/app/api/resources/person.rb"

module Api
  module V1
    class PeopleController < ApplicationController
      before_action :set_person, only: [:show, :edit, :update, :destroy]
      # 動作確認用にCSRFを無効化しています
      skip_before_action :verify_authenticity_token

      # GET /api/v1/people
      def index
        render json: Person.all
      end

      # GET /api/v1/people/1
      def show
        render json: PersonResource.new(Person.find(params[:id]))
      end

      # POST /api/v1/people
      def create
        @person = Person.new(person_params)
        if @person.save
          render json: @person
        else
          render json: @person.errors, status: :unprocessable_entity
        end
      end

      # PATCH/PUT /api/v1/people/1
      def update
        if @person.update(person_params)
          render json: @person
        else
          render json: @person.errors, status: :unprocessable_entity
        end
      end

      # DELETE /api/v1/people/1
      def destroy
        @person.destroy
        head :no_content
      end

      private
        # Use callbacks to share common setup or constraints between actions.
        def set_person
          @person = Person.find(params[:id])
        end

        # Never trust parameters from the scary internet, only allow the white list through.
        def person_params
          params.require(:person).permit(:name, :age)
        end
    end
  end
end
~~~

### routes.rb
~~~ruby
Rails.application.routes.draw do
  namespace :api, format: 'json' do
    namespace :v1 do
      resources :people
    end
  end
  resources :people
  root to: 'people#index'
end
~~~

### JSON Schemaを生成
Rails コンソールを利用してJSON Schemaを生成します。
出力した内容を `sample_api_v1.json` として保存します。

~~~bash
$ rails console
irb(main):002:0> puts Api::Schema.to_json_schema
{
  "description": "このスキーマ定義では、Sample API v1 のインターフェースをJSON Hyper Schema draft v4形式で表現しています。",
  "links": [
    {
      "method": "GET",
      "title": "{:href=>\"http://localhost:3000\", :rel=>\"self\"}"
    }
  ],
  "properties": {
    "people": {
      "links": [
        {
          "description": "全ての人物の一覧を取得します。",
          "href": "/api/v1/people",
          "method": "GET",
          "rel": "instances",
          "title": "list"
        },
        {
          "description": "人物を取得します。",
          "href": "/api/v1/people/:id",
          "method": "GET",
          "rel": "self",
          "title": "get"
        },
        {
          "description": "人物を登録します。",
          "href": "/api/v1/people/:id",
          "method": "POST",
          "rel": "self",
          "schema": {
            "properties": {
              "name": {
                "description": "人物の名前",
                "example": "tanaka",
                "type": "string"
              },
              "age": {
                "description": "人物の年齢",
                "example": 34,
                "type": "integer"
              }
            },
            "required": [
              "name",
              "age"
            ]
          },
          "title": "create"
        },
        {
          "description": "人物を更新します。",
          "href": "/api/v1/people/:id",
          "method": "PATCH",
          "rel": "self",
          "schema": {
            "properties": {
              "name": {
                "description": "人物の名前",
                "example": "tanaka",
                "type": "string"
              },
              "age": {
                "description": "人物の年齢",
                "example": 34,
                "type": "integer"
              }
            },
            "required": [
              "name",
              "age"
            ]
          },
          "title": "update"
        },
        {
          "description": "人物を削除します。",
          "href": "/api/v1/people/:id",
          "method": "DELETE",
          "rel": "self",
          "title": "delete"
        }
      ],
      "properties": {
        "id": {
          "description": "人物のid",
          "example": 1,
          "type": "integer"
        },
        "name": {
          "description": "人物の名前",
          "example": "tanaka",
          "type": "string"
        },
        "age": {
          "description": "人物の年齢",
          "example": 34,
          "type": "integer"
        }
      },
      "required": [
        "id",
        "name",
        "age"
      ],
      "title": "Person"
    }
  },
  "required": [
    "people"
  ],
  "title": "Sample API v1 JSON Schema"
}
~~~

## <i class="fa fa-child" style="font-size:1em;"></i> 動作確認
### <i class="fa fa-server" style="font-size:1em;"></i> APIサーバー起動
~~~bash
$ rails s -b 0.0.0.0
~~~

### <i class="fa fa-check-circle" style="font-size:1em;"></i> Curlで疎通確認
~~~bash
$ curl http://localhost:3000/people/1.json
{"id":1,"name":"tanaka","age":23,"created_at":"2015-07-15T07:29:24.210Z","updated_at":"2015-07-15T07:29:24.210Z"}
~~~

### <i class="fa fa-code" style="font-size:1em;"></i> Jsonismを使った、API呼び出しプログラムを作成

~~~ruby:jsonism_sample.rb
require 'json'
require 'jsonism'
require 'pp'
schema_body = File.read("sample_api_v1.json")
schema = JSON.parse(schema_body)
client = Jsonism::Client.new(schema: schema)
puts client.methods(false)
pp client.list_person.body.map(&:to_hash)
~~~

### <i class="fa fa-check-circle" style="font-size:1em;"></i> Jsonismの動作確認
~~~bash
$ ruby jsonism_sample.rb
list_person
get_person
create_person
update_person
delete_person
[{"id"=>1,
  "name"=>"tanaka",
  "age"=>23,
  "created_at"=>"2015-07-15T07:29:24.210Z",
  "updated_at"=>"2015-07-15T07:29:24.210Z"},
 {"id"=>2,
  "name"=>"suzuki",
  "age"=>42,
  "created_at"=>"2015-07-15T07:29:34.840Z",
  "updated_at"=>"2015-07-15T07:29:34.840Z"},
 {"id"=>3,
  "name"=>"honda",
  "age"=>99,
  "created_at"=>"2015-07-21T06:27:41.264Z",
  "updated_at"=>"2015-07-21T06:27:41.264Z"},
 {"id"=>4,
  "name"=>"seki",
  "age"=>123,
  "created_at"=>"2015-07-28T07:46:24.991Z",
  "updated_at"=>"2015-07-28T07:46:24.991Z"},
 {"id"=>6,
  "name"=>"hoge",
  "age"=>123,
  "created_at"=>"2015-07-29T08:13:34.077Z",
  "updated_at"=>"2015-07-29T08:13:34.077Z"},
 {"id"=>8,
  "name"=>"hoge",
  "age"=>123,
  "created_at"=>"2015-07-29T08:34:03.411Z",
  "updated_at"=>"2015-07-29T08:34:03.411Z"}]
~~~

## <i class="fa fa-book" style="font-size:1em;"></i> 関連エントリ
* <i class="fa fa-external-link-square" style="font-size:1em;"></i> [JsonWorldを利用してJSON SchemaとJSONを生成してみる](http://qiita.com/tbpgr/items/664a5038797e0f50d6ef)

## <i class="fa fa-book" style="font-size:1em;"></i> 外部資料
* <i class="fa fa-external-link" style="font-size:1em;"></i> [JsonWorld - GitHub](https://github.com/r7kamura/json_world)
* <i class="fa fa-external-link" style="font-size:1em;"></i> [Jsonism - GitHub](https://github.com/r7kamura/jsonism)
* <i class="fa fa-external-link" style="font-size:1em;"></i> [全てがJSONになる](http://r7kamura.hatenablog.com/entry/2014/06/10/023433)