Rails APIのレスポンスをキャメルケースに変換するテクニック

Railsは通常スネークケースを用いてコードを書きますが、JavaScriptなどのフロントエンドではキャメルケースを好む場合が多いです。
そのため、要件によっては、RailsでAPIを構築する際にスネークケースからキャメルケースへの変換が必要になる場合があります。
ここではそんな時の変換テクニックをいくつか紹介します。

# 1. ActiveModelSerializersの使用
`active_model_serializers`というGemを使用します。

### 設定
initializerに以下の設定を追加し、全体でキーをキャメルケースに変換するようにします。
``` config/initializers/active_model_serializers.rb
ActiveModel::Serializer.config.key_transform = :camel_lower
```
個別のレスポンスで異なる変換を行いたい場合は、render時に指定することも可能です。

### モデルのSerializer作成
それぞれのモデルに対してSerializerを作成します。
以下の例では、`UserSerializer`を定義し、`user_id`のキーを個別に指定しています。
``` ruby
class UserSerializer < ActiveModel::Serializer
  attribute :id, key: :user_id
  attribute :first_name
  attribute :last_name
end
```
このSerializerを使用してデータを返却する場合、以下のようにrenderメソッドで指定します。
``` ruby
 render json: user, serializer: UserSerializer
# 以下のような形式のJSONが返却されます
# {
#    userId: 1,
#    firstName: 'テスト',
#    lastName: '太郎'
# }
```
この方法は、複数のデータベースや親子関係にあるモデルにも適用できるため、柔軟に対応できます。
詳細な使い方については、[GitHub](https://github.com/rails-api/active_model_serializers)のドキュメントを参照してください。

# 2. deep_transform_keysメソッドを利用してハッシュキーを変換する
`ActiveSupport::Inflector`モジュールの`deep_transform_keys`メソッドを利用して、ハッシュのキーをキャメルケースに変換できます。

``` ruby
hash = {
  user_id: 1,
  user_name: 'テスト太郎',
  details: {
    last_login: '2024-06-30'
  }
}

camelized_hash = hash.deep_transform_keys { |key| key.to_s.camelize(:lower) }
# 以下のような形式のデータが得られます
# {
#   userId: 1,
#   userName: 'テスト太郎',
#   details: {
#     lastLogin: '2024-06-30'
#   }
# }
```
deep_transform_keysを使うことで、ネストされたハッシュも簡単にキャメルケースに変換できます。

# 3. Jbuilderを使用
Jbuilderを使うと、JSONレスポンスのキーをキャメルケースに変換することが簡単にできます。

### 個別に設定
適用したいjbuilderファイルの中に次の内容を追加してください。
``` xxx.json.jbuilder
json.key_format! camelize: :lower
```

### 一括で設定
`config/environment.rb`に以下の設定を入れると、全てのresponseのJSON keyがキャメルケースに変換されます。
``` config/environment.rb
Jbuilder.key_format camelize: :lower
```

# 4. ミドルウェアを作る
ミドルウェアを作成して、Railsアプリケーションのレスポンスをキャメルケースに変換することができます。

### ミドルウェアの実装
以下はキャメルケースに変換するミドルウェアの例です。
``` lib/middleware/camel_case_middleware.rb
module Middleware
  class CamelCaseMiddleware
    def initialize(app)
      @app = app
    end

    def call(env)
      status, headers, response = @app.call(env)

      if json_response?(headers)
        response_body = extract_body(response)
        new_body = convert_keys_to_camel_case(JSON.parse(response_body))
        response = [new_body.to_json]
        headers['Content-Length'] = response.first.bytesize.to_s
      end

      [status, headers, response]
    end

    private

    def json_response?(headers)
      headers['Content-Type']&.include?('application/json')
    end

    def extract_body(response)
      response_body = ''
      response.each { |part| response_body += part }
      response_body
    end
    
    def convert_keys_to_camel_case(value)
      case value
      when Array
        value.map { |v| convert_keys_to_camel_case(v) }
      when Hash
        value.each_with_object({}) do |(k, v), acc|
          acc[k.to_s.camelize(:lower)] = convert_keys_to_camel_case(v)
        end
      else
        value
      end
    end
  end
end
```

### ミドルウェアの設定
`config/application.rb`に以下のコードを追加します。
``` config/application.rb
require_relative '../lib/middleware/camel_case_middleware'
config.middleware.use Middleware::CamelCaseMiddleware
```

### 使用例
``` ruby
body = {
  user_id: 1,
  first_name: 'テスト',
  last_name: '太郎'
}
render json: body, status: :ok: 
# 以下のような形式のJSONが返却されます
# {
#    userId: 1,
#    firstName: 'テスト',
#    lastName: '太郎'
# }
```

# おわりに
これらの方法を活用することで、Railsアプリケーションでスネークケースからキャメルケースへの変換を効率的に行うことができます。
個人的には、`active_model_serializers`を使った方法か、`deep_transform_keys`を使った方法が好みです。プロジェクトの要件に応じて、適切な方法を選択してください！

# 参考
- [rails-api/active_model_serializers: ActiveModel::Serializer implementation and Rails hooks](https://github.com/rails-api/active_model_serializers)
- [ActiveSupport::Inflector - Rails API](https://api.rubyonrails.org/v7.1.3.2/classes/ActiveSupport/Inflector.html)
- [rails/jbuilder](https://github.com/rails/jbuilder)