5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-07-19

はじめに

Rails7.2がbetaフェーズとなっています。

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

後編も併せてご覧ください。

devcontainer

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

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

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

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

実際にやってみた

Rails7.1にはdevcontainerコマンドが存在しないのでエラーとなります。

rails7.1-app % rails devcontainer
Unrecognized command "devcontainer" (Rails::Command::UnrecognizedCommandError)

ではRails7.2で同じコマンドを実行してみます。

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

各ファイルの内容はそれぞれ次のとおりです。

.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"
}
.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
.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

しばらく待って以下のようにvscode → /workspaces/rails7.2-appのようになればOKです。
rails sでサーバーが立ち上がることを確認してみてください。
image.png

allow_browserメソッドによるブラウザのアクセス制限

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

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

rails newした時点でapp/controllers/application_controller.rbに記述されていました。

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

ブラウザ毎にバージョンを指定したり、特定のアクションだけへのアクセスだけを許可することもできるみたいです。

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で導入が提案されていました。

実際にやってみた

まずは検証用のPostコントローラーを作成

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の設定

config/routes.rb
Rails.application.routes.draw do
  get "posts", to: "posts#index"
end

この状態でChromeブラウザからhttp://127.0.0.1:3000/postsにアクセスできることを確認
image.png

app/controllers/application_controller.rbを修正しChromeブラウザからのアクセスを制限する

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

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

ログは以下のようになっています。

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

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

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

Edge
image.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で議論されていました。

デフォルトでPWAを搭載

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

rails newした時点でapp/views/pwa配下に以下の2ファイルが生成されます。

  • manifest.json.erb
  • service-worker.js

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

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"
}
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用のルーティングも自動で追加されています。

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で導入の提案が行われていました。

Rubocopがデフォルトで搭載

Rails7.2にはRubocopが搭載されるようになりました。

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

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

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

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

実際にやってみた

rails newした時点でrubocop-rails-omakaseGemfileに存在しています。

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コマンドを実行してみました。

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で確認できます。

実際に見てみる

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

ci.ymldependabot.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
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で導入が提案されていました。

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

実際にやってみた

rails newした時点でbrakemanGemfileに存在しています。

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は改変しています)

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つのスレッドを使用していましたが、この数が多すぎるのではないかと疑問が出されました。

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

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

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

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

実際に見てみる

Rails7.1の場合

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の場合

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に設定されています。

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

おわりに

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

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

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

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

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

後編も併せてご覧ください。

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

参考資料

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?