TES Blog

株式会社テクニカルエンジニアリングサポートに所属する社員が、自身が携わるテクノロジーやイベントに関する情報を発信しています。

ActiveModelSerializers をデフォルトのまま使い始めたら今さらページネーション情報を入れられなくなったあなたに贈る解決方法

はじめに

こんにちは。
Web エンジニアの Hayato Yamashita です。

普段の業務では PHP を使っていますが、弊社は Ruby 推し企業であることもあり、社内向けサービス開発や個人の趣味など、何かにつけて Ruby を触っています。

今回は Ruby on Rails で開発している時、ページネーションで困ったことを記事にしました。

この記事について

ページネーションとは、内容の多いページを複数に分割して表示する手法のことです。

Google 検索などにも用いられており、一度に読み込むデータ量を減らし、ページを早く表示できるメリットがあります。

このページネーション、非常に有用であり普遍的であるものなのですが、後から追加実装するのは大変です。

最初からページネーションを意識して開発していれば、そう難しいことではありません。
ページネーションは普遍的なものですし、サポートしてくれるプラグインもたくさんあります。

しかし、後から使う可能性があるのに、つい先延ばしにしてしまうことってありますよね。

本記事では、どのような経緯があって後からページネーションを追加しないといけなくなったのか。
そして、どのようにして後から追加したのか、詳しく記載していきます。

対象となる読者

  • Ruby on Rails を API として使っている
  • ActiveModelSerializers を導入している
  • 最初はページネーションしてなかったけど、新機能だけページネーションできるようにしたい

読んだら得られるもの

  • 後からでもページネーション機能を追加できるようになる
  • それがバッドノウハウであることを知れる(詳しくは後述)

まずは経緯

Ruby on Rails で Web アプリケーションを開発している時のことです。

Ruby on Rails を API モードで起動して、レスポンスとして JSON 文字列を返すため、利便性を考えて ActiveModelSerializers を導入。

特に設定はいじらず、デフォルトのままで使い始めました。

デフォルトのまま。
これが悪夢の始まりだったのです。

新規機能でページネーションする必要が出てきた

開発初期から作っていた機能は、画面に表示するデータの件数は少なく、ページネーション機能は不要だろうと考えられていました。

しかし、開発後期になり、ページネーションする必要のある追加機能が出てきたのです。

その追加機能は表示するデータ量が多く、通年でデータが溜まり続けるもので、ページネーションしないと将来的に画面表示が重くなりすぎる恐れがありました。

なら、ページネーションできるようにしよう。
そう簡単に考えて実装を始めて、すぐに問題にぶち当たりました。

後からページネーションできるようにしようとすると、その対応が及ぼす影響範囲が広すぎたのです。

そもそもページネーションするためには

少し掘り下げて説明します。

ページネーションするためには、それに関する情報をレスポンスに含める必要があります。

例えば、以下のイメージ。
articles がデータ本体、meta がページネーションに関する情報です。

{
  articles: [
    { id: 1, hoge: "fuga" },
    { id: 2, hoge: "piyo" }
  ],
  meta: {
    current_page: 3,
    next_page: 4,
    prev_page: 2,
    total_pages: 10
  }
}

上記の例では、現在3ページ目であり、前後のページ番号が分かり、合計ページ数も分かります。

これらの情報がレスポンスに含まれることによって、フロントエンドでリクエストするべき値を判断できるようになり、ページネーションすることができるのです。

デフォルト設定のままだとページネーションできない

そういうことであれば、上記のようにページネーションの情報をレスポンスに付与すれば良いのですが、これがうまくいかない。

ActiveModelSerializers は、デフォルトだと Attributes adapter の形式でレスポンスを整形します。

例えば、1件のデータの場合は、以下のイメージ。

{
  id: 1,
  hoge: "fuga"
}

n件(複数件)のデータの場合は、以下のイメージ。

[
  { id: 1, hoge: "fuga" },
  { id: 2, hoge: "piyo" }
]

上記の例のとおり、Attributes adapter はデータ本体を最上位で扱っています。

このままでは、ページネーションの情報を載せることができないのです。

後から追加するページネーションは単純ではない

ページネーションするためには、それに関する情報をレスポンスに載せる必要がある。
Attributes adapter では、それを載せることができない。

であれば adapter の設定を変えれば良いのですが、これも単純ではありません。

この adapter 設定は、そのシステムに対する共通的な設定となっています。
そのため、ひとたび設定を変えると、全てのエンドポイントに影響を及ぼすことになるのです。

全てのエンドポイントに影響があるということは、利用しているフロントサイドの全機能を修正しなければならない、ということ。
開発はだいぶ進んでしまっていたため、今さらそんな影響を与えることはできません。

つまり、取り返しがつかない状況だったのです。

どのように解決したのか

影響範囲を極力抑えるため、特定のエンドポイントだけ adapter を変更できないか考えました。

adapter の変更は必須事項。
他のエンドポイントに影響を与えないようにしなければならない。

つまり、新規機能で追加する特定のエンドポイントだけに適用する方法を探せば良いのです。

やり方は公式ガイドにあった

で、結論です。

github.com

上記の公式ガイドより転記。
以下のように render に対して adapter オプションを渡すことができました。

render json: @user_post, root: "admin_post", adapter: :json

上記の例では、adapter オプションに :json を渡しています。
こうすることにより、adapter の設定を Attributes から JSON に変更することができるのです。

これで、特定のエンドポイントだけ adapter を変更することができました。

もちろん、特定のエンドポイントだけ指定する形の実装ですので、他のエンドポイントは変わらず Attributes で機能します。

これで問題解決です!

でもちょっと待って

しかし、これで本当に良かったのでしょうか。

実はバッドノウハウ?

この対応によって「特定のエンドポイントだけページネーションすることができるようになった」と言えます。

しかし、言い換えれば「特定のエンドポイントだけ、レスポンスのフォーマットが異なる状態にしてしまった」とも言え、今後はその遺産を背負っていかなければならないのです。

公式ガイドの Configuration OptionsAdapters に、このやり方は載っていません。
そのことを考えれば、本来は推奨されていないことやっている、と分かっていただけますでしょうか。

おわりに

やり方を紹介する記事を書いておいてこう書くのも忍びないのですが、できるだけ本記事のやり方は真似しないでください。

「特定のエンドポイントだけ」という対応はナンセンスです。

本来であれば、最初からページネーションを使うことを考えて設定しておくべきでしょう。
そうでなければ、きちんとページネーションを使うようシステム全体を修正するべきです。

いざという時は、この記事の内容に従えば解決することはできます。
現実にはそういうこともあり、それを解決することも大事です。

けれど、良いエンジニアさんはちゃんと考えて、最初からページネーションできる設計しておきましょう。
さもなければ、未来のあなたが苦労することになるかもしません。

本当にどうしようもなくなった時だけ、本記事を参考にしてください。

どうかお気をつけて。