selializerを使ったAPIの作成と、リクエストスペックについて
jsonのserializerとは?
jsonを生成する仕組みのこと。
この生成したJSONのデータをレスポンスとして返す。
ActiveRecordは ActiveModel::Serialization を include している。
なので、そのまま以下のような記述もできる。
module Api module V1 class ArticlesController < BaseController def index articles = Article.all render json: articles end end end end
PostmanでAPIを叩くとJSONレスポンスが返ってきていることが分かる。
[今回の要件]
fast_jsonapiというserializerを使って、記事一覧のjson responseを返す。
fast_jsonapi/readme
Articleモデル。Userとは一対多の関係。
class Article < ApplicationRecord belongs_to :user has_many :comments, dependent: :destroy has_one_attached :eye_catch enum status: { draft: 0, in_review: 10, published: 20, archived: 30 } end
Articleモデルのシリアライザーを作る
$ be rails g serializer Article title contents status Running via Spring preloader in process 21798 create app/serializers/article_serializer.rb
シリアライザーの設定
(app/serializers/article_serializer.rb) class ArticleSerializer include FastJsonapi::ObjectSerializer attributes :title, :contents, :status belongs_to :user end
APIのコントローラ
Articleのデータから、jsonをシリアライズする。
コメントアウトしているが、to_jsonというメソッドを使うのもあり。
(app/controllers/api/v1/articles_controller.rb) # frozen_string_literal: true module Api module V1 class ArticlesController < BaseController def index articles = Article.all json_string = ArticleSerializer.new(articles).serialized_json # json_string = ArticleSerializer.new(articles).serializable_hash.to_json render json: json_string end end end end
PostmanでAPIを叩いてみると、ちゃんと記事一覧のJSONレスポンスが取れた!
{ "data": [ { "id": "1", "type": "article", "attributes": { "title": "title-1", "contents": "contents-1", "status": "draft" }, "relationships": { "user": { "data": { "id": "1", "type": "user" } } } }, { "id": "2", "type": "article", "attributes": { "title": "title-2", "contents": "contents-2", "status": "draft" }, "relationships": { "user": { "data": { "id": "2", "type": "user" } } } }, ... ...... ........
APIのリクエストスペック
- HTTP 動詞に対応するメソッド(get 、post 、delete 、 patch)を使う。
- HTTPのレスポンスのステータスコードと、実際のデータを含んだレスポンスボディを返せばよい。
以下【Rails】APIテストの書き方より、ステータスコードの定義を参照。
200: OK - リクエストは成功し、レスポンスとともに要求に応じた情報が返される。 401: Unauthorized - 認証失敗。認証が必要である。 403: Forbidden - 禁止されている。リソースにアクセスすることを拒否された。 404: Not Found - 未検出。リソースが見つからなかった。
テストコード
(spec/requests/api/v1/articles_spec.rb) require 'rails_helper' RSpec.describe "Api::V1::Articles", type: :request do describe "GET /api/v1/articles" do it "掲示板一覧を返す" do user = create(:user) create_list(:article, 10, user: user) get api_v1_articles_path, headers: { 'CONTENT_TYPE': 'application/json', 'ACCEPT': 'application/json' } # JSON.parse(body)でもいけたけど、response.bodyにしておく json = JSON.parse(response.body) expect(response).to have_http_status(200) expect(json["data"].count).to eq(10) end end end
JSON.parseについて
与えられた JSON 形式の文字列を Ruby オブジェクトに変換して返してくれる。
JSON形式の文字列からRubyのHashへ変換することで、APIで取得したJSONデータをRubyで使えるようになるって感じ。
JSON.parse(response.body)["data"]の結果
JSON.parse(response.body)["data"] => [{"id"=>"1", "type"=>"article", "attributes"=>{"title"=>"title-1", "contents"=>"contents-1", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"1", "type"=>"user"}}}}, {"id"=>"2", "type"=>"article", "attributes"=>{"title"=>"title-2", "contents"=>"contents-2", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"2", "type"=>"user"}}}}, {"id"=>"3", "type"=>"article", "attributes"=>{"title"=>"title-3", "contents"=>"contents-3", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"3", "type"=>"user"}}}}, {"id"=>"4", "type"=>"article", "attributes"=>{"title"=>"title-4", "contents"=>"contents-4", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"4", "type"=>"user"}}}}, {"id"=>"5", "type"=>"article", "attributes"=>{"title"=>"title-5", "contents"=>"contents-5", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"5", "type"=>"user"}}}}, {"id"=>"6", "type"=>"article", "attributes"=>{"title"=>"title-6", "contents"=>"contents-6", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"6", "type"=>"user"}}}}, {"id"=>"7", "type"=>"article", "attributes"=>{"title"=>"title-7", "contents"=>"contents-7", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"7", "type"=>"user"}}}}, {"id"=>"8", "type"=>"article", "attributes"=>{"title"=>"title-8", "contents"=>"contents-8", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"8", "type"=>"user"}}}}, {"id"=>"9", "type"=>"article", "attributes"=>{"title"=>"title-9", "contents"=>"contents-9", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"9", "type"=>"user"}}}}, {"id"=>"10", "type"=>"article", "attributes"=>{"title"=>"title-10", "contents"=>"contents-10", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"10", "type"=>"user"}}}}]
リクエストヘッダーをセットしておく。
これはヘッダ情報がないとテストが通らないことがあるため。ちょっと勉強不足ではある。
get api_v1_articles_path, headers: { 'CONTENT_TYPE': 'application/json', 'ACCEPT': 'application/json' }
headers情報をlet使って格納しておくのも良い
let(:headers) do { 'Content-Type' => 'application/json', 'Accept' => 'application/json' } end
数字を変数(numとか)に入れておくのもアリ
RSpec.describe 'Api::V1::Articles', type: :request do describe 'GET /articles' do let(:article_num) { 10 } before do user = create(:user) create_list(:article, article_num, user: user) end it 'returns fincle articles in json format' do get api_v1_articles_path, headers: { CONTENT_TYPE: 'application/json', ACCEPT: 'application/json', } expect(JSON.parse(body)['data'].count).to eq(article_num) expect(response).to be_successful expect(response).to have_http_status(:ok) end end end