🐍

camelCaseで来るリクエストパラメータのキーをRailsではsnake_caseで参照できるようにした話

Leaner Tecnologies 開発チームの RKTM です。

この記事では、フロントエンドはリクエストパラメータのキーを camelCase で送り、Rails 側ではそれを snake_case で参照できるようにした、という話をします。

バックエンドとフロントエンドでケースの不一致が引き起こす問題

現在開発中の Leaner 購買というプロダクトは下記の構成となっています。

  • バックエンド: Ruby/Rails
  • フロントエンド: TypeScript/React

一般的に、Ruby では Hash のキーは snake_case、TypeScript ではオブジェクトのキーは camelCase(lower camel case)が使われています。

Leaner 購買では、バックエンドからレスポンスを返す際には、Rails のモデルのキーを camelCase に変換して返しています。

{
  "company_info": {
    "company_name": "株式会社Leaner Technologies"
  }
}

を変換して以下の JSON にしています。

{
  "companyInfo": {
    "companyName": "株式会社Leaner Technologies"
  }
}

フロントエンドからのリクエストの際はオブジェクトのキーは camelCase のまま JSON で送信しています(上記の JSON の通り)。

Ruby/Railsに慣れた身には、paramsのキーはsnake_caseが入っていると思いがち問題

サーバーサイドを開発していると
「あれ、リクエストされた値がうまく保存されていないぞ。フロントエンドからうまく送れていないのかな?
(Rails をデバッガーで止めてみる)
ああー! params[:companyInfo] で参照しなくちゃいけないところを params[:company_info] と書いていた!」

ということが何度も繰り返されました(本当に何度も…。Ruby/Rails に慣れた身には切り替えが大変でした)。

他にも、params の値をモデルに設定する際にも params.deep_transform_keys(&:underscore) を各コントローラーで書かなくてはいけない状況でした。

あるべき姿: Ruby/Rails の世界では snake_case で扱いたい

このような徒労は避けたい!

ということで、Rails の世界では全て snake_case で扱えるよう、グローバルにリクエストパラメータのキーを変換する方法を探りました。

結果、StackOverflow の下記の回答のアプローチを採用しました。

https://stackoverflow.com/a/30557924

コードを以下に引用します。

# 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 のリクエストに対して以下の処理を行います。

  • Array ならその各要素のキーを deep_transform_keys で snake_case に変換
  • Array でないなら deep_transform_keys で snake_case に変換

ここで注意点として、二重配列のパラメータが送られてきた場合は中の Array に対して deep_transform_keys を呼び出すためエラーになりますが、そういったリクエストはまず送らないだろうということでほぼこの実装を踏襲しました。
回答者の方に感謝。

config/initializers 配下に定義しておくだけで、各コントローラーは snake_case でアクセスできるようになりました。

なお、デフォルトの動きは以下で定義されています。

https://github.com/rails/rails/blob/04972d9b9ef60796dc8f0917817b5392d61fcf09/actionpack/lib/action_dispatch/http/parameters.rb#L10-L15

Ruby/Railsの世界の秩序が保たれた

上記対応以降、Ruby/Rails のコードを書く上では snake_case のみを考慮すればよくなり、開発体験は健全なものとなっています。
徒労感がないことは非常に気持ち良いものです。
今後も、やりづらいこと、ミスしがちな点は、どんどん改善していきます。

余談:新メンバージョイン時にはモブプロは良い

最近 Leaner 購買の開発チームには新メンバーがジョインしたのですが、以下を目的にモブプロをしています。

  • ドメイン知識や技術の共有
  • メンバー間のコミュニケーションの活発化

今回の問題も
「モブプロ、なにかテーマありますか」
「あーそういえば params のキーの case をミスしがちなんですけど、なんとかならないですかね」
「お、じゃあやりましょう!」
という流れで着手してささっと実現できました。

その過程で、普段使っているツールの話でワイワイ盛り上がることができました。

採用

Leaner Technologies ではエンジニアを募集しています!
https://careers.leaner.co.jp/engineering

リーナーテックブログ

Discussion