きゃまなかのブログ

新卒5年目のWEBエンジニアです。仕事は運用メインで、空いた時間に開発しています。何故かブログを始めた次の日に会社の先輩に見つかりました。変な記事書くとダメ出し食らうので、いい記事書けるように頑張ります。

【Ruby on Rails】API のレスポンスを生成するメソッドを紹介

概要

Ruby on Rails 5 からは API モードが導入され、API 専用のアプリケーションを簡単に作成できるようになりました。

railsguides.jp

 

Ruby on Rails では CRUD と呼ばれる HTTP メソッドを利用して、RESTful なアプリケーション(API)を作成できるように設計されています。

CRUD 名 操作 HTTP メソッド Ruby on Rails のデフォルトアクション
CREATE 作成 POST create
READ 読み込み GET index, show, new, edit
UPDATE 更新 PUT/PATCH update
DELETE 削除 DELETE destroy

そのため、リクエストを受ける API のエンドポイント(URL)は比較的悩むことなく、スムーズに作成できるかと思います。

 

しかし、API のレスポンスは、自分で考えて作成しないといけません。

自分なりのルールがあれば良いのですが、無い場合は設計に悩みます。

今回、自分がレスポンスを作成する際に、過去のソースコードから毎回コピペして使っているメソッドがあるので、それを紹介します。 

紹介する前に

GET を利用したリクエストは、『読み込み(取得)』なので、リクエストされたデータをそのままレスポンスで返せば良いと思います。

ユーザ ID 1 番のデータに GET リクエストされた場合は、ユーザ ID 1番のデータを返すのが普通ですよね?

リクエストを投げた人は、そのユーザの名前だったり、性別だったり、年齢だったりを取得したいと思ったはずです。

では POST、PUT/PATCH、DELETE を利用したリクエストを受けた場合は、どうでしょうか?

多分なんですけど ...

一番知りたい情報は、作成・更新・削除 が成功したかどうかだと思います。

なので最低限、成功・失敗のステータスを返せればレスポンスとして成り立ちます。

失敗した場合は、失敗した理由(エラーメッセージ)も返せれば尚良いと思います。

しっかりやりたい人は作成・更新・削除したデータもレスポンスに含めるかもしれません。

コピペして使っているメソッド

GET 以外のリクエストを受けた際に利用します。

各 Controller でレスポンスを返す度に、render を記載するとコードが汚くなるので、共通メソッドとしてまとめました。

app/controllers/application_controller.rb などに記載して下さい。

# 200 Success
def response_success(class_name, action_name)
  render status: 200, json: { status: 200, message: "Success #{class_name.capitalize} #{action_name.capitalize}" }
end

# 400 Bad Request
def response_bad_request
  render status: 400, json: { status: 400, message: 'Bad Request' }
end

# 401 Unauthorized
def response_unauthorized
  render status: 401, json: { status: 401, message: 'Unauthorized' }
end

# 404 Not Found
def response_not_found(class_name = 'page')
  render status: 404, json: { status: 404, message: "#{class_name.capitalize} Not Found" }
end

# 409 Conflict
def response_conflict(class_name)
  render status: 409, json: { status: 409, message: "#{class_name.capitalize} Conflict" }
end

# 500 Internal Server Error
def response_internal_server_error
  render status: 500, json: { status: 500, message: 'Internal Server Error' }
end

レスポンスには HTTP ステータスコードとメッセージしか記載していません。

HTTPステータスコード - Wikipedia

HTTP ステータスは沢山ありますが、実際に API で使うのは限られます。

ケース・バイ・ケースで増やしたり減らしたりして下さい。

ステータスコード メッセージ 使う場面(例)
200 Success リクエストに成功した場合
400 Bad Request リクエストに必要なバラメータが欠けていた場合
401 Unauthorized トークンの認証が不正だった場合
404 Not Found 取得しようとしたデータが削除されていた場合
409 Conflict DB に重複するデータを格納しようとした場合
500 Internal Server Error DB や NW で障害が発生した場合

使い方

実際に利用しているソースコードを用意しました。

response_success などは何のアクションが成功したのか分かるように、引数にクラス名とアクション名を入れます。

  • response_success(:user, :create) でユーザの作成に成功した
  • response_success(:shop, :update) でお店の更新に成功した
  • response_success(:image, :delete) で画像の削除に成功した

などの情報がレスポンスで返ります。

class UsersController < ApplicationController
  before_action :set_user, only: :show

  # GET /users/1
  def show
    render json: response_fields(@user.to_json)
  end

  # POST /users
  def create
    @user = User.new(user_params)
    if @user.name.blank?
      # 必須パラメータが欠けている場合
      response_bad_request
    else
      if User.exists?(email: @user.email)
        # 既に登録済みのメールアドレスで登録しようとした場合
        response_conflict(:user)
      else
        if @user.save
          # ユーザ登録成功
          response_success(:user, :create)
        else
          # 何らかの理由で失敗
          response_internal_server_error
        end
      end
    end
  end

  private

  def set_user
    @user = User.find_by(id: params[:id])
    # 取得しようとしたユーザが存在しない
    response_not_found(:user) if @user.blank?
  end

  def user_params
    params.require(:user).permit(:name, :email)
  end
  
  # 他のコントローラでも除外したいフィールドが同じであれば、共通メソッドとして扱っても良い
  def response_fields(user_json)
    user_parse = JSON.parse(user_json)
    # レスポンスから除外したいパラメータ
    response = user_parse.except('created_at', 'updated_at', 'deleted_at')
    # JSON を見やすく整形して返す
    JSON.pretty_generate(response)
  end
end

共通メソッドにはしていませんが、最後の response_fields メソッドも結構コピペで使い回します。

created_at, updated_at, deleted_at は運用の観点で DB に必要なカラムなので、レスポンスで返す必要はないと思います。

動作確認

API にリクエストを投げて、返ってきたレスポンスの中身を確認します。

GET リクエストを受けた際には、pretty_generate  と言う JSON モジュールを使って、見やすく整形する事をオススメします。

module function JSON.#pretty_generate (Ruby 2.4.0)

GET リクエストに失敗した場合や、GET 以外のリクエストを受けた際のレスポンスは、HTTP ステータスコードとメッセージだけとなっています。

# ユーザの取得に成功
$ curl -X GET "http://localhost/v1/users/1"
{
  "id": 1,
  "name": "aaa",
  "email": "bbb"
}

# 取得しようとしたユーザが存在しない
$ curl -X GET "http://localhost/v1/users/9"
{"status":404,"message":"User Not Found"}

# 既に登録済みのメールアドレスで登録しようとした場合
$ curl -X POST "http://localhost/v1/users" -H "Content-Type: application/json" -d '{"user":{"name":"ccc","email":"bbb"}}'
{"status":409,"message":"User Conflict"}

# 必須パラメータが欠けている場合
$ curl -X POST "http://localhost/v1/users" -H "Content-Type: application/json" -d '{"user":{"name":"","email":""}}'
{"status":400,"message":"Bad Request"}

# ユーザ登録成功
$ curl -X POST "http://localhost/v1/users" -H "Content-Type: application/json" -d '{"user":{"name":"xxx","email":"yyy"}}'
{"status":200,"message":"Success User Create"}

おまけ

ページが存在しません

response_not_found は config/routes.rb に記載する事で、存在しないページにアクセスされた時のレスポンスにも利用できます。

get '*path', controller: 'application', action: 'response_not_found'

最後に

API のレスポンスを生成するメソッドを紹介しました。

GET リクエストでは、リクエストされたデータをそのまま返せば良いと思いますが、GET 以外のリクエストはどこまで情報を返せば良いか悩みます。

自分は HTTP ステータスコードとメッセージを返しています。

レスポンスでどこまでの情報を返すかは、開発者の優しさだと思います。

情報が多ければ多いほど、一緒に開発しているメンバーは API 周りの開発工数やテストの工数を削減できるかもしれません。

また、公開 API であれば利用者のトラブルが少なくなるかもしれません。

必要であれば、今回のメソッドを拡張して使って下さい。

今回、紹介していませんが、サーバ運用の観点からレスポンスの結果をログに吐き出したりするのも良いと思います。