概要
Ruby on Rails 5 からは API モードが導入され、API 専用のアプリケーションを簡単に作成できるようになりました。
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 ステータスは沢山ありますが、実際に 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 であれば利用者のトラブルが少なくなるかもしれません。
必要であれば、今回のメソッドを拡張して使って下さい。
今回、紹介していませんが、サーバ運用の観点からレスポンスの結果をログに吐き出したりするのも良いと思います。