Rails7.1で廃止されるconfig.action_dispatch.return_only_request_media_type_on_content_typeについて調べた

## 対象読者

- Railsをアップグレードしていて `config.action_dispatch.return_only_request_media_type_on_content_type` って何？となった人
- Rails 7.0 から Rails 7.1 へアップグレードするにあたって `ActionDispatch::Request#content_type` の挙動が変わって困っている人
- Rails 7.0 以降での `ActionDispatch::Request#content_type` の実装が知りたい人

なお、新旧の `ActionDispatch::Request#content_type` の定義箇所については記載していますが、細かい挙動の差異については、この記事では紹介していません。

## `config.action_dispatch.return_only_request_media_type_on_content_type` とは？

Rails 7.0 から、デフォルトでは `ActionDispatch::Request#content_type` が Content-Type ヘッダをそのまま返すようになっています。

https://railsguides.jp/upgrading_ruby_on_rails.html#actiondispatch-request-content-type%E3%81%8Ccontent-type%E3%83%98%E3%83%83%E3%83%80%E3%83%BC%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E8%BF%94%E3%81%99%E3%82%88%E3%81%86%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F

詳しくは上記ガイドを参照してほしいのですが、Rails 7.0 より前とあとでは `ActionDispatch::Request#content_type` の挙動が大きく違います。
従来の挙動を維持したい場合は `config.action_dispatch.return_only_request_media_type_on_content_type = true` とすればよいのですが、Rails 7.1 でこの設定が意味をなさなくなり、対応が必要になってきます。

## どうすればよいか

https://github.com/rails/rails/blob/69cb72e25212570ab80ccaf092e0807c37bd823f/guides/source/upgrading_ruby_on_rails.md#actiondispatchrequestcontent_type-now-returns-content-type-header-as-it-is

に書いてある通りなのですが、 `ActionDispatch::Request#media_type` を `ActionDispatch::Request#content_type` の代わりに利用してください。

Rails 7.0 までで、必要に応じて `ActionDispatch::Request#content_type` 利用箇所を `ActionDispatch::Request#media_type` に置き換え、 `config.action_dispatch.return_only_request_media_type_on_content_type` を削除しても問題なく動作するかを確認すると良いはずです。

## `ActionDispatch::Request#content_type` の変更履歴

頑張って調べてみました。

### Rails 7.0 より前

Rails 7.0 より前の `ActionDispatch::Request#content_type` は `ActionDispatch::Http::MimeNegotiation#content_type` として、

https://github.com/rails/rails/blob/86864c2f6661d3aa93b07f833b2900d241568cb7/actionpack/lib/action_dispatch/http/mime_negotiation.rb#L35-L37

で定義されていました。

`ActionDispatch::Http::MimeNegotiation` を `ActionDispatch::Request` で `include` し、 `ActionDispatch::Request#content_type` として呼び出せるようになっていた、ということです。

https://github.com/rails/rails/blob/b1ec311a8fa3b8e1ac5cdcfd850b793f8cf5aecd/actionpack/lib/action_dispatch/http/request.rb#L20-L28

### Rails 7.0

しかし Rails 7.0 からは、以下のように `config.action_dispatch.return_only_request_media_type_on_content_type` オプションで条件分岐するようになっています。

https://github.com/rails/rails/blob/778eab826538be6da355d848aecaea2245e3b8ce/actionpack/lib/action_dispatch/http/mime_negotiation.rb#L36-L47

この条件分岐で、デフォルトでは `super` の `content_type` を呼ぶようになっています。

### Rails 7.1 から

Rails 7.1 からは、Rails 7.0 において存在していた条件分岐が消え、 `ActionDispatch::Http::MimeNegotiation#content_type` も消え、 `config.action_dispatch.return_only_request_media_type_on_content_type` 設定を使うと警告が出るようになっています。

https://github.com/rails/rails/commit/689b27773396c06b49333de9f6e9d0127f16d6ea

Rails 7.1.5.1 時点のコードを見ても、 `ActionDispatch::Http::MimeNegotiation#content_type` が存在していないことを確認できるはずです。

https://github.com/rails/rails/blob/14c115b120ed089331ff3dc13f36bd9129ced33d/actionpack/lib/action_dispatch/http/mime_negotiation.rb#L20-L32

### Rails 7.2 から

Rails 7.2 からは、 `config.action_dispatch.return_only_request_media_type_on_content_type` を設定しようとする警告すら消え、 `ActionDispatch::Request#content_type` の変更の移行期間が終了しています。

https://github.com/rails/rails/commit/d088d9132f634e88bfa92ea82978d5c21d0c8c32

Rails 7.2.2.1 時点のコードを見ても、 `config.action_dispatch.return_only_request_media_type_on_content_type` にまつわるコードが完全に無くなっていることを確認できるはずです。

https://github.com/rails/rails/blob/33beb0a38db1c058123a8e3cc298cad918adfe32/actionpack/lib/action_dispatch/http/mime_negotiation.rb

## 新しい `ActionDispatch::Request#content_type` の定義を探してみる

上でも書いたとおり、 Rails 7.0 からは、デフォルトでは `super` の `content_type` を呼ぶようになっています。

この `super` の `content_type` がどこで定義されているのかモヤモヤする方もいると思うので、解説します。

Rails 7.1 において、 `ActionDispatch::Request` では、以下のように各種 module が include されています。

https://github.com/rails/rails/blob/v7.1.5.1/actionpack/lib/action_dispatch/http/request.rb#L19-L27

この何処かで `ActionDispatch::Request#content_type` の実体が定義されているはずですが、調べるのが面倒なので Rails 7.1 の環境を用意し、 `ActionDispatch::Request#content_type` の実体がどこで定義されているかを確認してみます。

local 環境に直で `rails new` して Rails 7.1 の環境を用意すると、local 環境が汚れてしまうので、

https://hub.docker.com/r/bitnami/rails

の image をお借りして確認します。

```bash
$ docker run -it bitnami/rails:7.1.3 bash
root@737436f37791:/app# rails -v
Rails 7.1.3.2
root@737436f37791:/app# rails new hoge
root@737436f37791:/app# cd hoge/
root@737436f37791:/app/hoge# rails c
Loading development environment (Rails 7.1.5.1)
irb(main):001> ActionDispatch::Request.new({}).method(:content_type).source_location
=> ["/opt/bitnami/ruby/lib/ruby/gems/3.2.0/gems/rack-3.1.8/lib/rack/request.rb", 308]
```

どうやら rack で定義されているものを利用しているようです。

rackのバージョンは若干違うかもしれませんが、コードを確認してみると、たしかにその辺りに `content_type` メソッドがあります。

https://github.com/rack/rack/blob/49d4ed033f9c6d0bdba7b2a181437589049dbf7f/lib/rack/request.rb#L308-L311

`Rack::Request::Helpers` module で定義されたこの `content_type` メソッド が、 Rails 側の

https://github.com/rails/rails/blob/v7.1.5.1/actionpack/lib/action_dispatch/http/request.rb#L19-L27

で `include Rack::Request::Helpers` され、 `ActionDispatch::Request#content_type` が呼び出し可能になっている、というわけです。

つまり、Rails 7.0 からは、 rack の実装を利用するようになった、ということですね。

## まとめ

ここまでをざっくりまとめると

- Rails 7.0 から `ActionDispatch::Request#content_type` の挙動が変わる
  - `config.action_dispatch.return_only_request_media_type_on_content_type` フラグで設定可能だが、Rails 7.1 から完全に挙動が変わる
  - `ActionDispatch::Request#media_type` を `ActionDispatch::Request#content_type` の代わりに利用すればよい
- Rails 7.0 より前の `ActionDispatch::Request#content_type` は `ActionDispatch::Http::MimeNegotiation#content_type` で定義されている
- Rails 7.0 からの `ActionDispatch::Request#content_type` は `Rack::Request::Helpers#content_type` で定義されている

ということになります。

ここまでご覧いただきありがとうございました。
この記事が、あなたのRailsアップグレード調査に少しでも役立つと嬉しいです。