.../articles/

Railsでキャメルケースのリクエストを扱うために

RailsでAPIを実装する場合にRailsのコードではスネークケースを使いつつ、クライアントにはキャメルケースのリクエスト(あるいはレスポンス)を扱っているように振る舞うための方法を調べました。

背景

過去の記事でOpenAPIをベースにした開発を進めているという話題がありました。

そのなかでフロントの組み込みの段階でリクエストがあったのが、「リクエスト、レスポンスのJSONのキーをキャメルケースにして欲しい」というものでした。

簡単に説明すると、上の例がスネークケースで一般的にRubyで変数を扱うときによく使われます。
一方で下の例はキャメルケースと呼ばれ、JavaScriptなどでよく使われます。

{"first_name": "foo", "last_name": "bar", "note": "snake case"}
{"firstName": "foo", "lastName": "bar", "note": "camel case"}

※変数名の命名規則は基本的に絶対的なルールがあるものではないですが、おおよそプログラミング言語などによって好まれるパターンがあります

Railsで素直にJSONを返すようなAPIサーバーを実装をするとリクエストもレスポンスもキーは基本的にスネークケース。
しかしフロントエンドではキーにキャメルケースを使いたいし、いちいちリクエストでデータを投げる場合やレスポンスをパースする場合に変換するのは面倒。

ということでRailsでリクエスト、レスポンスのJSONのキーをキャメルケースとして上手く扱う方法を調べてみました。


やったこと

前提として以下のようなことを意識しつつ、レスポンスとリクエストについてそれぞれ方法を考えてみました。

  • Railsのコード内では可能な限りスネークケースを使いたい
  • キャメルケースとスネークケースの変換をするコードはできるだけ集約したい
  • パフォーマンスに大きく影響しない

レスポンス

順番的にはリクエストから書くべきかもしれませんが、レスポンスを扱う場合のほうが簡単だったので先に紹介します。
ただし前提としてActiveModelSerialzersを使っているとします。

ActiveModelSerializerを使っている場合は key_transform を指定することで簡単にレスポンスのキーのパターンを変えることができます。
以下のドキュメントを参考に、今回のケースであれば :camel_lower を指定するだけでOKです。

リクエスト

先に挙げた例のようにPOSTリクエストでキーがキャメルケースのJSONを送ったとします。

{"firstName": "foo", "lastName": "bar", "note": "camel case"}

これをRailsで処理するときに以下のようにキーがスネークケースのデータになっていてほしいとするとどうするのがよいでしょうか。

{"first_name": "foo", "last_name": "bar", "note": "snake case"}

できればControllerでリクエストパラメータを扱う時点で変換された状態になっていてほしい、ということで調べてみたところStack Overflowに同じ趣旨の質問と回答がありました。

上記記事より抜粋したのが以下のコードです。(Railsのバージョンは6とします)

# File: config/initializers/json_param_key_transform.rb
# Transform JSON request param keys from JSON-conventional camelCase to
# Rails-conventional snake_case:
ActionDispatch::Request.parameter_parsers[:json] = lambda { |raw_post|
  # Modified from action_dispatch/http/parameters.rb
  data = ActiveSupport::JSON.decode(raw_post)

  # Transform camelCase param keys to snake_case
  if data.is_a?(Array)
    data.map { |item| item.deep_transform_keys!(&:underscore) }
  else
    data.deep_transform_keys!(&:underscore)
  end

  # Return data
  data.is_a?(Hash) ? data : { '_json': data }
}

とりあえず書いてあるとおりに試したところ、JSONのキーがキャメルケースからスネークケースに変換されたリクエストパラメータを受け取ることができました。

一体このコードは何をしているのでしょうか?

Modified from action_dispatch/http/parameters.rb

このコメントを参考にRailsのコードを確認してみました。

このなかで DEFAULT_PARSERS が定義されており、JSONをパースするメソッドが定義されているようです。

      DEFAULT_PARSERS = {
        Mime[:json].symbol => -> (raw_post) {
          data = ActiveSupport::JSON.decode(raw_post)
          data.is_a?(Hash) ? data : { _json: data }
        }
      }

つまり記事の中で紹介しているコードはJSONをパースするメソッドを deep_transform_keys!(&:underscore) を使って上書きすることで、パースしたパラメータのキーを強制的にスネークケースにするためのものと理解しました。


まとめ

紹介したようにリクエスト、レスポンスに関する設定を行うことでRailsではキーをスネークケースで扱いつつ、APIにリクエストするクライアントにはキーがスネークケースであるように振る舞うことができるようになりました。

当然キャメルケース以外でも設定を変更することで対応可能なので、状況に応じて使い分けが簡単にできそうです。

JSON以外のパラメータ

Railsの扱うパラメータはリクエストパラメータ以外にパスパラメータとクエリパラメータがあります。

パスパラメータはURLに含まれておりキーを指定するものではないので、Railsのルーティングでスネークケースとして定義されていれば問題はありません。

一方でクエリパラメータはクライアントでキーを指定するため同様の問題が起こりえるのですが、上記の変換する設定では変換されません。

# クライアントはuserIdとするがRailsではuser_idとして扱いたい
GET /notes?userId=1

上記の例は適当なのでクエリパラメータを使わずにパス設計を見直すことで解決できるかもしれません。

しかし、クエリパラメータが必要になったときにクライアントに部分的にスネークケースを使うように強要したり、キーの命名に1単語しか使えないという制約が発生するのは避けたいです。

このあたりを上手く解決する方法は引き続き検討していこうかと思っています。

追記

あらためて調べてみると before_action を使ってパラメータ全体を変換するアプローチができるようです。
なんとなく最初にイメージしていたのはこちらに近く、この方が方法としては分かりやすいように思いました。(未検証)

まだどのアプローチをとるかは決めかねているので、探りつつ決めていきたいです。

.../articles/

Articles

記事

AWS AmplifyにmonorepoのNext.js(App Router)をデプロイする

AWS AmplifyにmonorepoのNext.js(App Router)をデプロイする

monorepo管理しているNext.jsをAmplifyにデプロイしようとした際にいくつか躓く内容があったのでまとめておきます。

リモートワーク・オンライン会議でも、スムーズに制作を進めるために大切なこと[資料編]

リモートワーク・オンライン会議でも、スムーズに制作を進めるために大切なこと[資料編]

コロナ禍の影響により、リモートワークの導入をおこなっている制作会社も多く、実際に弊社でも導入しています。

売れるECサイトデザインを作るために。参考にしたいおしゃれな事例の探し方。

売れるECサイトデザインを作るために。参考にしたいおしゃれな事例の探し方。

売れるECサイトのデザインは、「この形式」という決まりはありません。ECサイトで売り上げを上げるなら、しっかりとしたコンセプトと、コンセプトを決定するまでのリサーチが必要です。

制作会社の考える、業務効率化ツールのおすすめ。個人でも使いやすいサービスなど。

制作会社の考える、業務効率化ツールのおすすめ。個人でも使いやすいサービスなど。

新型コロナウイルス感染拡大の影響で、リモートワークが主流になり、弊社でも週のほとんどは各自宅で作業をしています。

Figmaでデザインのコミット履歴を残せるプラグイン【Thought Recorder】をリリースしました

Figmaでデザインのコミット履歴を残せるプラグイン【Thought Recorder】をリリースしました

Figmaを利用するWebデザイナーの助けになれると嬉しいです。使い方は本記事をご覧ください。

ECの構築方法、おすすめのECサービス。

ECの構築方法、おすすめのECサービス。

ファッションや家電、スーパーの買い物でさえもECサイトを利用することが当たり前になりました。加えて新型コロナウイルスの影響もあり、弊社にも「どんなプラットフォームを利用したら良いか」「どれくらいコストがかかるのか」などECに関するさまざまなご相談を頂きます。

FastAPIのスキーマクラスをOpenAPIから生成する方法

FastAPIのスキーマクラスをOpenAPIから生成する方法

PythonでAPIを構築する要件があり、フレームワークに比較的モダンなFastAPIを採用しました。FastAPIはバックエンドの開発を行えば自動でOepnApi定義を生成する機能が備わっていますが、今回はこれを使わず、事前に用意したOepnApi定義からFastAPIで利用するスキーマクラスを生成する方法を紹介します。

Laravel 日本一解りやすい全文検索のマイグレーション記載方法解説

Laravel 日本一解りやすい全文検索のマイグレーション記載方法解説

Laravel + MySQLで全文検索を実装する

とあるPythonのソースで sys.path.append としたく無かった話

とあるPythonのソースで sys.path.append としたく無かった話

とあるプロジェクトのとあるソースコードのレビューをしてた時、「ソースコードの参照がうまくいってなかったので修正しました」とレビュー依頼がきました。 ディレクトリ構造 ``` module L __init__.py L main.py L tests L __init__.py L test_main.py ``` ソースコード ``` python tests/test_main.py sys.path.append(os.path.abspath("..")) from main import fuga ``` 今まで案件でPythonに触れる機会も結構ありましたが、なんとなく使ってきた部分も多く、この書き方が良いのか悪いのか判別できなかったので、改めてPythonのモジュールのインポートに関して調べてみたのでブログにしました。普段PHPを書いている事が多くPythonに関して何も分からないので初心者向けの内容になっていると思います。

GiFT1号目新卒デザイナーの2021年振り返り

GiFT1号目新卒デザイナーの2021年振り返り

いつの間に、年末ですね。入社してもう、9ヶ月も立っていたようです。2021年の振り返りを記事にしました。

すべての記事

お問い合わせ