Rails7.2の新機能と変更点をまとめる【前編】

## はじめに
Rails7.2がbetaフェーズとなっています。

リリースに先立ち、新機能や変更点などをまとめつつ、実際に検証してみました。

[後編](https://qiita.com/Kazuyaa/items/f026de54fef70feaf632)も併せてご覧ください。

## devcontainer
Rails7.2ではVSCode上で使えるdevcontainerを設定する機能が追加されました。

`rails new`を実行する際に`--devcontainer`オプションを付与できるようになっています。

また`devcontainer`コマンドを実行すれば、既存のRailsアプリケーションでも開発用コンテナの設定が可能です。

以下のIssueで導入の議論がされていました。

https://github.com/rails/rails/issues/50566

### 実際にやってみた
Rails7.1には`devcontainer`コマンドが存在しないのでエラーとなります。
```bash
rails7.1-app % rails devcontainer
Unrecognized command "devcontainer" (Rails::Command::UnrecognizedCommandError)
```

ではRails7.2で同じコマンドを実行してみます。
```bash
rails7.2-app % rails devcontainer
Generating Dev Container with the following options:
app_name: rails72_app
database: sqlite3
active_storage: true
redis: true
system_test: true
node: false
      create  .devcontainer
      create  .devcontainer/devcontainer.json
      create  .devcontainer/Dockerfile
      create  .devcontainer/compose.yaml
        gsub  test/application_system_test_case.rb
```
`.devcontainer`ディレクトリとその配下に以下の3ファイルが生成されました。

- `devcontainer.json`
- `Dockerfile`
- `compose.yaml`

各ファイルの内容はそれぞれ次のとおりです。
```json:.devcontainer/devcontainer.json
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby
{
  "name": "rails72_app",
  "dockerComposeFile": "compose.yaml",
  "service": "rails-app",
  "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",

  // Features to add to the dev container. More info: https://containers.dev/features.
  "features": {
    "ghcr.io/devcontainers/features/github-cli:1": {},
    "ghcr.io/rails/devcontainer/features/activestorage": {},
    "ghcr.io/rails/devcontainer/features/sqlite3": {}
  },

  "containerEnv": {
    "CAPYBARA_SERVER_PORT": "45678",
    "SELENIUM_HOST": "selenium",
    "REDIS_URL": "redis://redis:6379/1"
  },

  // Use 'forwardPorts' to make a list of ports inside the container available locally.
  "forwardPorts": [3000, 6379],

  // Configure tool-specific properties.
  // "customizations": {},

  // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
  // "remoteUser": "root",


  // Use 'postCreateCommand' to run commands after the container is created.
  "postCreateCommand": "bin/setup"
}
```
```Dockerfile:.devcontainer/Dockerfile
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.2.2
FROM ghcr.io/rails/devcontainer/images/ruby:$RUBY_VERSION
```
```yaml:.devcontainer/compose.yaml
name: "rails72_app"

services:
  rails-app:
    build:
      context: ..
      dockerfile: .devcontainer/Dockerfile

    volumes:
    - ../..:/workspaces:cached

    # Overrides default command so things don't shut down after the process ends.
    command: sleep infinity

    # Uncomment the next line to use a non-root user for all processes.
    # user: vscode

    # Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
    # (Adding the "ports" property to this file will not forward from a Codespace.)
    depends_on:
    - selenium
    - redis

  selenium:
    image: seleniarm/standalone-chromium
    restart: unless-stopped

  redis:
    image: redis:7.2
    restart: unless-stopped
    volumes:
    - redis-data:/data

  
volumes:
  redis-data:
```
これを使ってdevcontainerを実際に立ち上げてみます。

VSCodeの画面右下の通知から「コンテナーで再度開く」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325281/d332c14f-baf1-c8f1-4e53-4a1b16b070a2.png)

しばらく待って以下のように`vscode → /workspaces/rails7.2-app`のようになればOKです。
`rails s`でサーバーが立ち上がることを確認してみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325281/77e9bda5-a0ad-6bba-43ea-c2414782de65.png)

## allow_browserメソッドによるブラウザのアクセス制限
コントローラーの各アクションにアクセスできるブラウザとそのバージョンを制限できる機能が追加されました。

`allow_browser`メソッドをコントローラー内に記述することで実現できます。

`rails new`した時点で`app/controllers/application_controller.rb`に記述されていました。
```ruby:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
  allow_browser versions: :modern
end
```
ブラウザ毎にバージョンを指定したり、特定のアクションだけへのアクセスだけを許可することもできるみたいです。
```ruby
class ApplicationController < ActionController::Base
  # ブラウザ毎にバージョンを指定（ieはアクセスさせない）
  allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
end

class MessagesController < ApplicationController
  # onlyオプションで特定のアクションだけを許可する
  allow_browser versions: { opera: 104, chrome: 119 }, only: :show
end
```

以下のIssueで導入が提案されていました。

https://github.com/rails/rails/issues/50445

### 実際にやってみた
まずは検証用のPostコントローラーを作成
```bash
rails7.2-app % rails g controller posts index
      create  app/controllers/posts_controller.rb
       route  get "posts/index"
      invoke  erb
      create    app/views/posts
      create    app/views/posts/index.html.erb
      invoke  test_unit
      create    test/controllers/posts_controller_test.rb
      invoke  helper
      create    app/helpers/posts_helper.rb
      invoke    test_unit
```

`config/routes.rb`の設定
```ruby:config/routes.rb
Rails.application.routes.draw do
  get "posts", to: "posts#index"
end
```

この状態でChromeブラウザから`http://127.0.0.1:3000/posts`にアクセスできることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325281/e0d6d447-fe16-c061-1e3d-688a4fe64f79.png)

`app/controllers/application_controller.rb`を修正しChromeブラウザからのアクセスを制限する
```ruby:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # Chromeからはアクセスできないよう設定
  allow_browser versions: { chrome: false }
end
```
Chromeブラウザから`http://127.0.0.1:3000/posts`にアクセスしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325281/5300929d-47b0-3e05-e561-7d7cf2d552e4.png)


たしかにアクセスが制限されました！

ログは以下のようになっています。
```bash
Started GET "/posts" for 127.0.0.1 at 2024-07-18 10:47:10 +0900
Processing by PostsController#index as HTML
  Rendering public/406-unsupported-browser.html
  Rendered public/406-unsupported-browser.html (Duration: 0.4ms | GC: 0.0ms)
Filter chain halted as #<Proc:0x000000010828cb58 /opt/homebrew/lib/ruby/gems/3.2.0/gems/actionpack-7.2.0.beta3/lib/action_controller/metal/allow_browser.rb:48 (lambda)> rendered or redirected
Completed 406 Not Acceptable in 3ms (Views: 1.1ms | ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.0ms)
```

念のためSafariからも`http://127.0.0.1:3000/posts`にアクセスしてみます。

Safari
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325281/562c5be2-3491-8876-135d-39966f2a7c42.png)

Safariではアクセスできました！

ちなみに余談ですが、EdgeはChromiumベースのブラウザなので`chrome: false`と書いた場合、Edgeからのアクセスも制限されてしまいます。

Edge
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325281/7b8c0148-e9c8-f529-05c4-d6f3282a0709.png)

これはEdgeがChromiumベースのブラウザだからです。

Chromiumベースのブラウザは、User-Agent文字列に "Chrome" という文字列を含んでいます。

そして`allow_browser`メソッドはUser-Agent文字列に含まれる値でどのブラウザからのアクセスなのか？を判定しています。

よって`chrome: false`と書くとMicrosoft Edgeからのアクセスも制限されるのです。


## Ruby3.1以上をサポート
Rails7.1.3.4ではRuby2.7以上をサポートしていましたが、Rails7.2がサポートするのはRuby3.1以上です。

またRubyのサポートに関するポリシーも変更されたようです。
### 以前までのポリシー
Railsのメジャーバージョンアップ（例：6.0から7.0へ）の時にサポートするバージョンの削除が行われていた
### これからのポリシー
Railsのマイナーバージョンアップ（例：7.0から7.1へ）の際にも、サポートが終了したRubyバージョンの削除が行われるかもしれない

サポートするRubyのバージョンとこれらのポリシーについては以下のPRで議論されていました。

https://github.com/rails/rails/pull/50491

## デフォルトでPWAを搭載
デフォルトでPWA用のファイルが作成されるようになりました。

`rails new`した時点で`app/views/pwa`配下に以下の2ファイルが生成されます。
- manifest.json.erb
- service-worker.js

それぞれの中身は次のとおりです。

```ruby:manifest.json.erb
{
  "name": "Rails72App",
  "icons": [
    {
      "src": "/icon.png",
      "type": "image/png",
      "sizes": "512x512"
    },
    {
      "src": "/icon.png",
      "type": "image/png",
      "sizes": "512x512",
      "purpose": "maskable"
    }
  ],
  "start_url": "/",
  "display": "standalone",
  "scope": "/",
  "description": "Rails72App.",
  "theme_color": "red",
  "background_color": "red"
}
```

```javascript:service-worker.js
// Add a service worker for processing Web Push notifications:
//
// self.addEventListener("push", async (event) => {
//   const { title, options } = await event.data.json()
//   event.waitUntil(self.registration.showNotification(title, options))
// })
// 
// self.addEventListener("notificationclick", function(event) {
//   event.notification.close()
//   event.waitUntil(
//     clients.matchAll({ type: "window" }).then((clientList) => {
//       for (let i = 0; i < clientList.length; i++) {
//         let client = clientList[i]
//         let clientPath = (new URL(client.url)).pathname
// 
//         if (clientPath == event.notification.data.path && "focus" in client) {
//           return client.focus()
//         }
//       }
// 
//       if (clients.openWindow) {
//         return clients.openWindow(event.notification.data.path)
//       }
//     })
//   )
// })
```

またPWA用のルーティングも自動で追加されています。
```ruby:config/routes.rb
Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
  # Can be used by load balancers and uptime monitors to verify that the app is live.
  get "up" => "rails/health#show", as: :rails_health_check

  # Render dynamic PWA files from app/views/pwa/*
  get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
  get "manifest" => "rails/pwa#manifest", as: :pwa_manifest

  # Defines the root path route ("/")
  # root "posts#index"

  get "posts", to: "posts#index"
end
```

以下のIssueで導入の提案が行われていました。

https://github.com/rails/rails/issues/50455

## Rubocopがデフォルトで搭載
Rails7.2にはRubocopが搭載されるようになりました。

`rubocop-rails-omakase`というgemのルールがデフォルトで適用されます。

https://github.com/rails/rubocop-rails-omakase

Rubocop自体はRubyの静的解析ツールですが、`rubocop-rails-omakase`は特にRails開発のためのルールを提供するものです。

スタイル強制が軽いため、Rubyならではの高い表現力を損なわなずに最低限のリンターとして機能させられる、というのが`rubocop-rails-omakase`の採用理由だそうです。

Rubocop導入の是非を議論しているのは以下のPRで確認できます。

https://github.com/rails/rails/issues/50456

### 実際にやってみた
`rails new`した時点で`rubocop-rails-omakase`が`Gemfile`に存在しています。

```ruby:Gemfile
source "https://rubygems.org"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.2.0.beta3"
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"
# Use sqlite3 as the database for Active Record
gem "sqlite3", ">= 1.4"
# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"
# Use Redis adapter to run Action Cable in production
# gem "redis", ">= 4.0.1"

# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ windows jruby ]

# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false

# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

group :development, :test do
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"

  # Static analysis for security vulnerabilities [https://brakemanscanner.org/]
  gem "brakeman", require: false

  # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
  gem "rubocop-rails-omakase", require: false
end

group :development do
  # Use console on exceptions pages [https://github.com/rails/web-console]
  gem "web-console"
end

group :test do
  # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
  gem "capybara"
  gem "selenium-webdriver"
end
```

`rubocop`コマンドを実行してみました。

```bash
rails7.2-app % rubocop
Inspecting 31 files
....C......................C...

Offenses:

app/controllers/application_controller.rb:4:4: C: [Correctable] Layout/TrailingEmptyLines: Final newline missing.
end
   
test/application_system_test_case.rb:14:1: C: [Correctable] Layout/EmptyLinesAroundClassBody: Extra empty line detected at class body end.

31 files inspected, 2 offenses detected, 2 offenses autocorrectable
```

たしかに使えました！これは便利そうですね。

一方でインストールをスキップしたい場合は`rails new myapp --skip_rubocop`とオプションを指定できるようにもなっています。

## GitHub ActionsのCI設定ファイル
GitHub ActionsのCI設定ファイルがデフォルトで作成されるようになります。

前述した`Rubocop`や後述する`brakeman`がデフォルトで含まれるようになったため、これらのチェックを自動化するための機能を含める流れとなりました。

また自動テストの習慣をRails開発者に教える意図も含まれているようです

議論の過程は以下のIssueで確認できます。

https://github.com/rails/rails/issues/50502

### 実際に見てみる

```bash
.github/
 ─ workflows/
   ─ ci.yml
 ─ dependabot.yml
```

`ci.yml`と`dependabot.yml`の中身はそれぞれ以下のとおりでした。

```yml:ci.yml
name: CI

on:
  pull_request:
  push:
    branches: [ main ]

jobs:
  scan_ruby:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version
          bundler-cache: true

      - name: Scan for security vulnerabilities in Ruby dependencies
        run: bin/brakeman --no-pager

  scan_js:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version
          bundler-cache: true

      - name: Scan for security vulnerabilities in JavaScript dependencies
        run: bin/importmap audit

  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version
          bundler-cache: true

      - name: Lint code for consistent style
        run: bin/rubocop -f github

  test:
    runs-on: ubuntu-latest

    # services:
    #  redis:
    #    image: redis
    #    ports:
    #      - 6379:6379
    #    options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
    steps:
      - name: Install packages
        run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libjemalloc2 libsqlite3-0 libvips

      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version
          bundler-cache: true

      - name: Run tests
        env:
          RAILS_ENV: test
          # REDIS_URL: redis://localhost:6379/0
        run: bin/rails db:test:prepare test test:system

      - name: Keep screenshots from failed system tests
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: screenshots
          path: ${{ github.workspace }}/tmp/screenshots
          if-no-files-found: ignore
```
```yml:dependabot.yml
version: 2
updates:
- package-ecosystem: bundler
  directory: "/"
  schedule:
    interval: daily
  open-pull-requests-limit: 10
- package-ecosystem: github-actions
  directory: "/"
  schedule:
    interval: daily
  open-pull-requests-limit: 10
```

`ci.yml`では以下の内容をチェックしています。
- Rubyの依存関係にまつわる脆弱性
- JavaScriptの依存関係にまつわる脆弱性
- リンターによるコードスタイル
- テスト実行
- 失敗したテストのスクリーンショット保存

`dependabot.yml`ではGemfileとGitHub Actionsの設定をチェックし、更新が必要な依存関係やアクションがないかをチェックしています。

更新が必要な場合、最大10件までプルリクエストを作成します。

またこれらのファイル生成が不要な場合は`rails new . --skip_ci`とオプションを指定できます。

## Brakemanがデフォルトで搭載
Railsアプリケーションの脆弱性を検出してくれる`brakeman`というgemもデフォルトで含まれるようになりました。

これはRuby on Railsを初めて使う新参者がセキュリティ面のミスをしないように、という配慮のようです。

以下のIssueで導入が提案されていました。

https://github.com/rails/rails/issues/50501

先ほどのGitHub ActionsのCIフローにも`brakeman`によってセキュリティ脆弱性を検知するジョブが組み込まれていましたね。

### 実際にやってみた
`rails new`した時点で`brakeman`が`Gemfile`に存在しています。

```ruby:Gemfile
source "https://rubygems.org"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.2.0.beta3"
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"
# Use sqlite3 as the database for Active Record
gem "sqlite3", ">= 1.4"
# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"
# Use Redis adapter to run Action Cable in production
# gem "redis", ">= 4.0.1"

# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ windows jruby ]

# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false

# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

group :development, :test do
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"

  # Static analysis for security vulnerabilities [https://brakemanscanner.org/]
  gem "brakeman", require: false

  # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
  gem "rubocop-rails-omakase", require: false
end

group :development do
  # Use console on exceptions pages [https://github.com/rails/web-console]
  gem "web-console"
end

group :test do
  # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
  gem "capybara"
  gem "selenium-webdriver"
end
```

`brakeman`コマンドを実行してみました（Application Pathは改変しています）
```bash
rails7.2-app % brakeman
Loading scanner...
Processing application in /Users/hogehoge/rails7.2-app
Processing gems...                      
[Notice] Detected Rails 7 application
Processing configuration...             
[Notice] Escaping HTML by default
Parsing files...                        
Detecting file types...                 
Processing initializers...              
Processing libs...                      
Processing routes...                    
Processing templates...                 
Processing data flow in templates...    
Processing models...                    
Processing controllers...               
Processing data flow in controllers...  
Indexing call sites...                  
Running checks in parallel...
 - CheckBasicAuth
 - CheckBasicAuthTimingAttack
 - CheckCrossSiteScripting
 - CheckContentTag
 - CheckCookieSerialization
 - CheckCreateWith
 - CheckCSRFTokenForgeryCVE
 - CheckDefaultRoutes
 - CheckDeserialize
 - CheckDetailedExceptions
 - CheckDigestDoS
 - CheckDynamicFinders
 - CheckEOLRails
 - CheckEOLRuby
 - CheckEscapeFunction
 - CheckEvaluation
 - CheckExecute
 - CheckFileAccess
 - CheckFileDisclosure
 - CheckFilterSkipping
 - CheckForgerySetting
 - CheckHeaderDoS
 - CheckI18nXSS
 - CheckJRubyXML
 - CheckJSONEncoding
 - CheckJSONEntityEscape
 - CheckJSONParsing
 - CheckLinkTo
 - CheckLinkToHref
 - CheckMailTo
 - CheckMassAssignment
 - CheckMimeTypeDoS
 - CheckModelAttrAccessible
 - CheckModelAttributes
 - CheckModelSerialize
 - CheckNestedAttributes
 - CheckNestedAttributesBypass
 - CheckNumberToCurrency
 - CheckPageCachingCVE
 - CheckPathname
 - CheckPermitAttributes
 - CheckQuoteTableName
 - CheckRansack
 - CheckRedirect
 - CheckRegexDoS
 - CheckRender
 - CheckRenderDoS
 - CheckRenderInline
 - CheckResponseSplitting
 - CheckRouteDoS
 - CheckSafeBufferManipulation
 - CheckSanitizeConfigCve
 - CheckSanitizeMethods
 - CheckSelectTag
 - CheckSelectVulnerability
 - CheckSend
 - CheckSendFile
 - CheckSessionManipulation
 - CheckSessionSettings
 - CheckSimpleFormat
 - CheckSingleQuotes
 - CheckSkipBeforeFilter
 - CheckSprocketsPathTraversal
 - CheckSQL
 - CheckSQLCVEs
 - CheckSSLVerify
 - CheckStripTags
 - CheckSymbolDoSCVE
 - CheckTemplateInjection
 - CheckTranslateBug
 - CheckUnsafeReflection
 - CheckUnsafeReflectionMethods
 - CheckValidationRegex
 - CheckVerbConfusion
 - CheckWeakRSAKey
 - CheckWithoutProtection
 - CheckXMLDoS
 - CheckYAMLParsing
Checks finished, collecting results...
Generating report...

== Brakeman Report ==

Application Path: /Users/hogehoge/rails7.2-app
Rails Version: 7.2.0.beta3
Brakeman Version: 6.1.2
Scan Date: 2024-07-18 22:59:23 +0900
Duration: 0.350629 seconds
Checks Run: BasicAuth, BasicAuthTimingAttack, CSRFTokenForgeryCVE, ContentTag, CookieSerialization, CreateWith, CrossSiteScripting, DefaultRoutes, Deserialize, DetailedExceptions, DigestDoS, DynamicFinders, EOLRails, EOLRuby, EscapeFunction, Evaluation, Execute, FileAccess, FileDisclosure, FilterSkipping, ForgerySetting, HeaderDoS, I18nXSS, JRubyXML, JSONEncoding, JSONEntityEscape, JSONParsing, LinkTo, LinkToHref, MailTo, MassAssignment, MimeTypeDoS, ModelAttrAccessible, ModelAttributes, ModelSerialize, NestedAttributes, NestedAttributesBypass, NumberToCurrency, PageCachingCVE, Pathname, PermitAttributes, QuoteTableName, Ransack, Redirect, RegexDoS, Render, RenderDoS, RenderInline, ResponseSplitting, RouteDoS, SQL, SQLCVEs, SSLVerify, SafeBufferManipulation, SanitizeConfigCve, SanitizeMethods, SelectTag, SelectVulnerability, Send, SendFile, SessionManipulation, SessionSettings, SimpleFormat, SingleQuotes, SkipBeforeFilter, SprocketsPathTraversal, StripTags, SymbolDoSCVE, TemplateInjection, TranslateB:
```

ちゃんと使えました。

これも`rubocop-rails-omakase`と同様に、インストールをスキップしたい場合は`rails new . --skip_brakeman`とオプションを指定できます。

## Pumaのデフォルトのスレッド数が5→3へ変更
Rails7.2からPumaのデフォルトのスレッド数が5スレッドから3スレッドに変更されます。

これまでPumaはデフォルトで5つのスレッドを使用していましたが、この数が多すぎるのではないかと疑問が出されました。

スレッドの数はアプリケーションの性能に大きく影響しており、数を減らせば減らすほど、個々の処理の速度（レイテンシ）は向上が見込めますが、同時に処理できる量（スループット）は下がってしまいます。

そのため適切なスレッド数を選び、応答速度と処理能力の程よいバランスを取ることが求められます。

議論の過程では１スレッドや２スレッドにする案も登場していますが、最終的な落とし所として3スレッドへの変更で同意が取られました。

議論の過程は以下のIssueで確認できます。

https://github.com/rails/rails/issues/50450

### 実際に見てみる
Rails7.1の場合
```ruby:config/puma.rb
# This configuration file will be evaluated by Puma. The top-level methods that
# are invoked here are part of Puma's configuration DSL. For more information
# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.

# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

# Specifies that the worker count should equal the number of processors in production.
if ENV["RAILS_ENV"] == "production"
  require "concurrent-ruby"
  worker_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count })
  workers worker_count if worker_count > 1
end

# Specifies the `worker_timeout` threshold that Puma will use to wait before
# terminating a worker in development environments.
worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
port ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
environment ENV.fetch("RAILS_ENV") { "development" }

# Specifies the `pidfile` that Puma will use.
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart
```

`max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }`の部分でスレッド数を5に設定しています。

続いてRails7.2の場合
```ruby:config/puma.rb
# This configuration file will be evaluated by Puma. The top-level methods that
# are invoked here are part of Puma's configuration DSL. For more information
# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.

# Puma starts a configurable number of processes (workers) and each process
# serves each request in a thread from an internal thread pool.
#
# The ideal number of threads per worker depends both on how much time the
# application spends waiting for IO operations and on how much you wish to
# to prioritize throughput over latency.
#
# As a rule of thumb, increasing the number of threads will increase how much
# traffic a given process can handle (throughput), but due to CRuby's
# Global VM Lock (GVL) it has diminishing returns and will degrade the
# response time (latency) of the application.
#
# The default is set to 3 threads as it's deemed a decent compromise between
# throughput and latency for the average Rails application.
#
# Any libraries that use a connection pool or another resource pool should
# be configured to provide at least as many connections as the number of
# threads. This includes Active Record's `pool` parameter in `database.yml`.
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count

# Specifies the `environment` that Puma will run in.
rails_env = ENV.fetch("RAILS_ENV", "development")
environment rails_env

case rails_env
when "production"
  # If you are running more than 1 thread per process, the workers count
  # should be equal to the number of processors (CPU cores) in production.
  #
  # Automatically detect the number of available processors in production.
  require "concurrent-ruby"
  workers_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.available_processor_count })
  workers workers_count if workers_count > 1

  preload_app!
when "development"
  # Specifies a very generous `worker_timeout` so that the worker
  # isn't killed by Puma when suspended by a debugger.
  worker_timeout 3600
end

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
port ENV.fetch("PORT", 3000)

# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart

# Only use a pidfile when requested
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]
```

`threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)`の部分でデフォルトのスレッド数が3に設定されています。

コメントアウト部分にも、スループットとレイテンシのバランスを考慮して３スレッドに設定している旨が書かれていますね。

## おわりに
この記事では、Rails 7.2の新機能や変更点について、一部の内容を実際に試しながら解説しました。

betaフェーズにあるバージョンの新機能を追うのは初めてですが、とても勉強になりました。

単に追加される機能を知るだけでなく、IssueやPRなどを通じて背景や議論の過程も含めて把握することが大切だと感じます。

しかしRails7.2の新機能はまだこれだけではありません。

~~現在、続編を執筆中です。~~

[後編](https://qiita.com/Kazuyaa/items/f026de54fef70feaf632)も併せてご覧ください。

またこの記事に関して間違っている点がありましたら、教えていただけますと幸いです。

## 参考資料
https://edgeguides.rubyonrails.org/7_2_release_notes.html