Rails
active_model_serializers

Railsのactive_model_serialiserについて学ぶ④_100DaysOfCodeチャレンジ14日目(Day_14:#100DaysOfCode)

はじめに

この記事はTwitterで人気のハッシュタグ#100DaysOfCodeをつけて、
100日間プログラミング学習を続けるチャレンジに挑戦した14日目の記録です。

動作環境

  • ruby 2.4.1
  • Rails 5.0.1

現在学習している内容のリポジトリ

https://github.com/yuta-ushijima/notebook-api-on-rails

本日学んだこと

  • RESTについて
  • HATEOASについて
  • AMS(active_model_serialiser)を使ってJSONレスポンスにlink情報を含める

RESTとは?

RESTは、Representational State Transfer(レプレゼ-ショナル・ステート・トランスファー)の略。

Representationalは日本語で「表現の」などを意味する形容詞なので、ステート(状態)を表現し、転送するといった戸田奈津子訳になります。

具体的にはWEBサービスの設計方法を指していて、RESTに基づいたサイトはHTTPメソッドを使うと、取得/登録/更新/削除のようなデータの送受信ができるようになります。

詳しく知りたい方は、以下の記事が参考になると思いますので、一読することをおすすめします。

REST入門 基礎知識

HATEOASって?

HATEOASは、Hypermedia As The Engine Of Application State(ハイパーメディア・アズ・ザ・エンジン・オブ・アプリケーション・ステート)の略。

戸田奈津子訳にすると、「アプリ側でデータ変更を行う際に、利用可能なアクション一覧を含んだJSONなどのデータ」といった感じかな。

例えば、localhost:3000/contacts/1のようなAPIのエンドポイントがあったとして、GETでデータを取得した時、以下のようなJSONがレスポンスとして返ってきたとしましょう。

{
    "data": {
        "id": "1",
        "type": "contacts",
        "attributes": {
            "name": "中島 光",
            "email": "rhianna_walsh@maggio.net",
            "birthdate": "2016-05-02",
            "birthday": "2016-05-02T00:00:00+09:00"
        },
        "relationships": {
            "kind": {
                "data": {
                    "id": "1",
                    "type": "kinds"
                },
                "links": {
                    "kind": "http://localhost:3000/kinds/1"
                }
            },
            "phones": {
                "data": [
                    {
                        "id": 1,
                        "number": "080-6512-3933",
                        "contact-id": 1,
                        "created-at": "2018-06-26T14:37:28.719Z",
                        "updated-at": "2018-06-26T14:37:28.748Z"
                    },
                    {
                        "id": 2,
                        "number": "090-5775-8292",
                        "contact-id": 1,
                        "created-at": "2018-06-26T14:37:28.766Z",
                        "updated-at": "2018-06-26T14:37:28.771Z"
                    },
                    {
                        "id": 3,
                        "number": "070-4705-9581",
                        "contact-id": 1,
                        "created-at": "2018-06-26T14:37:28.778Z",
                        "updated-at": "2018-06-26T14:37:28.784Z"
                    }
                ]
            },
            "address": {
                "data": {
                    "id": 1,
                    "street": "1236 丸山Ways",
                    "city": "大岩崎村",
                    "contact_id": 1,
                    "created_at": "2018-06-26T14:37:28.074Z",
                    "updated_at": "2018-06-26T14:37:28.074Z"
                }
            }
        },
        "links": {
            "self": "http://localhost:3000/contacts/1",
            "kind": "http://localhost:3000/kinds/1"
        }
    },
    "included": [
        {
            "id": "1",
            "type": "kinds",
            "attributes": {
                "description": "Goodmorning!"
            }
        }
    ],
    "meta": {
        "author": "ウェブ系ウシジマくん"
    }
}

終盤にあるlinksという部分がこのAPIのエンドポイントを指しています。

このエンドポイントに対してGETやPATCHをはじめとしたHTTPメソッドでアクションをすれば、JSONに含まれる属性を使ってデータの送受信が可能。

contactのnameを変えたい場合は、

curl -X "PATCH" -d "{ name: 'ウェブ系ウシジマ' }"  http://localhost:3000/contacts/1

このようにcurlやPostmanなどで更新できます。

HATEOASに基づいて設計すれば、ユーザーに対してデータをどのようにして利用できるかを示すことができますね。

もっと詳しく知りたい方は、以下の記事を参考にしてみてください。

RailsのAPIにHATEOASを散りばめてみる : RESTの拡張、HATEOASの詳解と実装例

Railsを使ってJSONのレスポンスにlinksを含める実装

さて、先ほどのHATEOSの説明の際に表示したJSONレスポンスのなかで、linksという箇所がありましたよね。

"links": {
            "self": "http://localhost:3000/contacts/1",
            "kind": "http://localhost:3000/kinds/1"
        }

これをRails側で実装するときに、最初にいじるべきはserializer.rbですね。

このserializer.rbを次のようなコードを追記します。

class ContactSerializer < ActiveModel::Serializer
  attributes :id, :name, :email, :birthdate #, :author

  link(:self) { contact_url(object.id) }
  link(:kind) { kind_url(object.kind.id) }

end

linkの引数にモデル名をシンボルで渡して、あとは対象のモデルに対するパスを書くだけですね。

ただ、contact_urlはそのままだと「URLのパスがおかしいぞ!」というエラーを吐き出します。

そこで、config/environments/development.rbに次のコードを追記しましょう。

Rails.application.routes.default_url_options = {
    host: 'localhost',
    port: 3000
  }
end

追加結果は以下です。

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  Rails.application.routes.default_url_options = {
    host: 'localhost',
    port: 3000
  }
end

この設定を追加せずにlink情報をJSONのレスポンスに含めたい場合は、serializer.rbに記述するパスの指定を次のように変更します。

class ContactSerializer < ActiveModel::Serializer
  attributes :id, :name, :email, :birthdate #, :author

  link(:self) { contact_path(object.id) }
  link(:kind) { kind_path(object.kind.id) }

end

ただし、このように記述すると、JSONのレスポンスが返ってくる際にスキーマとリソースが省略され、エンドポイントだけになる点が違います。

"links": {
            "self": "/contacts/1",
            "kind": "/kinds/1"
        }

番外編:URLとURIって何が違うの?

URLとは?

URLは、Uniform Resource Locator(ユニフォーム リソース ロケーター)の略。

Locatorとある通り、リソースがある場所についての書き方を定義したもので、URLにアクセスすることで画像やファイルのある場所へたどり着くことができます。

URIとは?

URIは、Uniform Resource Identifier(ユニフォーム リソース アイデンティファ-)の略で、場所に限らず、リソースをユニークに判別するためのデータ書式を定義したもの

つまり、URLよりもURIの方が広い意味で使われていることになります。

詳しく知りたい方は以下の記事が参考になるでしょう。

URLとURIは何が違うの? どちらが正しい呼び方?

まとめ

linkオプションを使用することで、JSON自体のURLをレスポンスのフィールドに含めることができるということですね。

参考リンク

https://en.wikipedia.org/wiki/HATEOAS

https://ja.wikipedia.org/wiki/Representational_State_Transfer

https://en.wikipedia.org/wiki/Representational_state_transfer#Hypermedia_as_the_engine_of_application_state_(HATEOAS)