ITエンジニアによるITエンジニアのためのブログ

Ruby on Rails 6.1から7.0へのアップグレード手順

はじめに

本記事では、Ruby on Rails 6.1から7.0へアップグレードする手順を解説します。

公式サイトのアップグレードガイドに加えて、現場で遭遇しがちな問題の解決策や、より詳細な解説を盛り込みました。ぜひ、アップグレード作業の際にお役立てください。

目次

  1. アップグレード前に行なうこと
    • 自動テストのカバレッジ向上と修正
    • Rubyのバージョンアップ
  2. アップグレード手順
    • Gemfileの更新
    • rails app:updateタスクの実行
  3. デフォルト設定適用とコード修正
    • config.load_defaults 7.0 への段階的な移行
    • レンダリングでファイル拡張子が含まれているとエラーになる問題
    • オープンリダイレクトの例外
    • ActionView::Helpers::UrlHelper#button_to の挙動変更
    • Spring gemの非デフォルト化
    • Sprocketsの使用継続または削除
    • Zeitwerkオートローダーの必須化
    • ハッシュ関数の変更に伴うクッキーローテーターの設置
    • ActiveSupport::Digest のダイジェストクラス変更
    • スキーマダンプへのRailsバージョン記載
    • stylesheet_link_tag のmedia属性
    • Cookie serializerフォーマットの変更
    • Ruby 3.1/3.2エラー対策用Gemの削除
  4. 本番環境へのリリース
  5. まとめ

1. アップグレード前に行うこと

自動テストのカバレッジ向上と修正

どのバージョンのアップグレードでも同様ですが、まず自動テストのカバレージを可能な限り高めておきましょう。テストが失敗している箇所があれば、アップグレード前に修正します。テストが不完全な状態では、アップグレードによって生じた不具合を検知するのが困難になります。

Rubyのバージョンアップ

Rails 7.0は、Ruby 2.7.0以上を要求します。お使いのRubyが2.7.0未満の場合は、先にRubyのバージョンアップが必要です。

本記事の執筆時点(2025年6月)では、Ruby 3.1以前のバージョンはすべてEOL(End of Life)となっています。セキュリティの観点からも、Ruby 3.2以上へのアップグレードを強く推奨します

なお、影響範囲を限定するため、RubyのバージョンアップとRailsのバージョンアップは、それぞれ独立したブランチで作業し、個別にリリースすることをおすすめします。

2. アップグレード手順

Gemfileの更新

Gemfileに記載されているRailsのバージョンを7.0系に書き換えます。

その後、bundle updateを実行します。

# Gemfile
gem 'rails', '~> 7.0.0
# Bash
bundle update

bundle updateが終わらない場合

bundle updateが完了しない場合は、以下の方法で解決できることがあります。

  1. verboseオプションを付ける: bundle update --verbose を実行すると、処理の詳細が出力され、問題箇所を特定できる場合があります。
  2. Railsのみアップデートする: bundle update rails を実行し、まずRails本体と、その直接の依存Gemのみをアップデートします。

他のGemがRails 7.0未満に依存している場合

特定のGemがRails 7.0未満のバージョンに依存していると、以下のような依存関係のエラーでアップデートが失敗します。

Bundler could not find compatible versions for gem "activemodel":
  In Gemfile:
    rails (~> 7.0.0) was resolved to 7.0.0, which depends on
      activemodel (= 7.0.0)

    validates_timeliness was resolved to 6.0.1, which depends on
      activemodel (>= 6.0.0, < 7)

この場合、Gemfileで該当Gemのバージョン指定を一旦緩めます。

-gem 'validates_timeliness', '~> 6.0.0'
+gem 'validates_timeliness'

そして、bundle updateでRailsと同時にアップデート対象として指定します。

# Bash
bundle update rails validates_timeliness

rails app:updateタスクの実行

Railsにはバージョンアップ用のapp:updateタスクが用意されています。これを実行して、設定ファイルを更新します。

# Bash
rails app:update

Loggerエラーが発生する場合

このタスクの実行中に、以下のLogger関連のエラーが発生することがあります。

uninitialized constant ActiveSupport::LoggerThreadSafeLevel::Logger

これは、concurrent-rubyの特定のバージョンでloggerの依存関係が変更されたことに起因します。解決策はいくつかありますが、concurrent-rubyのバージョンをエラーが発生しないバージョン(例:1.3.4など)に固定する方法が確実です。

# Gemfile
gem 'concurrent-ruby', '1.3.4'

# Bash
bundle

もう一つの方法は、config/boot.rbの先頭でloggerrequireすることですが、Sprocketsを使い続ける場合はこの方法ではassets:precompile時に問題が再発する可能性があります。

# config/boot.rb
require "logger" # この行を追加
require "bundler/setup"
require "bootsnap/setup

このエラーはRails 7.1では解消されているため、7.1へアップグレードするまでの一時的な対応となります。

ファイルの更新

app:updateタスクは、新しいバージョンのデフォルトファイルと現在のファイルを比較し、上書きするかどうかをファイルごとに尋ねてきます。

Overwrite config/boot.rb? (enter "h" for help) [Ynaqdhm]

各キーの意味は以下の通りです。dキーで差分を必ず確認し、判断することをおすすめします。

Y: 上書きする
n: スキップする
a: このファイル以降、すべて上書きする
q: 終了する
h: ヘルプを表示する
d: 差分を表示する
m: マージツールを起動する

もし誤ってファイルを上書きしてしまっても、このタスクは何回でも実行できます。git restore <file>でファイルを元に戻し、再度タスクを実行してください。

rails app:updateで更新対象となるファイルは以下の通りです。

  • config/boot.rb
  • config/application.rb
  • config/environments/development.rb
  • config/environments/production.rb
  • config/environments/test.rb
  • config/initializers/assets.rb
  • config/initializers/content_security_policy.rb
  • config/initializers/cors.rb
  • config/initializers/filter_parameter_logging.rb
  • config/initializers/inflections.rb
  • config/initializers/permissions_policy.rb
  • bin/rails
  • bin/rake
  • bin/setup
  • db/schema.rb

なお、本コマンドで比較や上書きで使われるRailsのデフォルトファイル(tt拡張子のテンプレートファイル)は、以下のディレクトリに格納されています。必要であればコマンドを一度中止し、現在のファイルとテンプレートファイルをじっくり比較してどうするか検討しても良いでしょう。

# Bash
cd `bundle show railties`

cd lib/rails/generators/rails/app/templates

また、自身がRails6.1でどのようなカスタマイズを行ったかを確認する場合は、デフォルトファイルがRails 6.1の上記と同様のフォルダに格納されていますので、比較してみましょう。

このタスクの一環でrails active_storage:updateも実行され、マイグレーションファイルが生成されることがあります。忘れずにrails db:migrateを実行しましょう。

# db/migrate/remove_not_null_on_active_storage_blobs_checksum.active_storage.rb

class RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0]
  def change
    return unless table_exists?(:active_storage_blobs)

    change_column_null(:active_storage_blobs, :checksum, true)
  end
end
rails db:migrate

3. デフォルト設定適用とコード修正

config.load_defaults 7.0 への段階的な移行

rails app:updateを実行すると、config/initializers/new_framework_defaults_7_0.rbというファイルが生成されます。この時点では、アプリケーション全体の設定はまだ6.1のままです。

# config/application.rb
config.load_defaults 6.1

安全にアップグレードを進めるため、この設定は6.1のまま維持し、new_framework_defaults_7_0.rb内のコメントアウトされた設定を一つずつ有効化して動作確認と修正を進めるのが推奨されるアプローチです。

レンダリングでファイル拡張子が含まれているとエラーになる問題

Rails 7.0では、renderrender_to_stringメソッドで渡すパスに.html.erbのような拡張子が含まれているとActionView::MissingTemplateエラーが発生します。

# Rails 6.1では非推奨警告、7.0でエラー
render(partial: 'example.html.erb')
# => ActionView::MissingTemplate

このエラーは、パスから拡張子を削除し、代わりにformatsオプションでファイルタイプをシンボルで指定することで解決できます。

render(partial: 'example', formats: :html)

render_to_string(
  layout: 'application',
  template: "examples/report",
  formats: :pdf
)

オープンリダイレクトの例外

安全でない外部サイトへのリダイレクト(オープンリダイレクト)防止のため、redirect_to メソッドを使った外部サイトへのリダイレクトは例外が発生するようになりました。

redirect_to 'https://some_external_website'
=> ActionController::Redirecting::UnsafeRedirectError

この変更はnew_framework_defaults_7_0.rb内の以下の設定で有効になります。

Rails.application.config.action_controller.raise_on_open_redirects = true

例外を回避するためには、allow_other_hostオプションをtrueに設定する必要があります。

redirect_to 'https://some_external_website', allow_other_host: true

但し、paramsなどユーザー入力値をリダイレクト先にしている場合は脆弱性に該当しますので、許可された外部サイトのホワイトリストを作成して照合するなど、注意が必要です。

ActionView::Helpers::UrlHelper#button_toの挙動変更

button_toメソッドの挙動が2点変更されました。

  1. HTTPメソッドの変更: 永続化されたレコード(DBに保存済みのオブジェクト)に対して使うと、HTTPメソッドがPOSTからPATCHに変わります。
  2. HTMLタグの変更: 出力されるHTMLが<input type="submit">から<button type="submit">に変わります。
# Rails 6.1
= button_to 'Post my object', Example.first
=> <form class="button_to" method="post" action="/examples/1"><input type="submit" value="Post my object"><input type="hidden" name="authenticity_token" value="my_token"></form>

Rails 7.0
= button_to 'Post my object', Example.first
=> <form class="button_to" method="post" action="/examples/1"><input type="hidden" name="_method" value="patch" autocomplete="off"><button type="submit">Post my object</button><input type="hidden" name="authenticity_token" value="my_token" autocomplete="off"></form>

この変更はnew_framework_defaults_7_0.rb内の以下の設定で有効になります。

Rails.application.config.action_view.button_to_generates_button_tag = true

RESTの観点からはPATCHが適切ですが、もしPOSTを維持したい場合は、method: :postを明示的に指定してください。

# Rails 7.0
= button_to 'Post my object', Example.first, method: :post
=> <form class="button_to" method="post" action="/examples/1"><button type="submit">Post my object</button><input type="hidden" name="authenticity_token" value="my_token" autocomplete="off"></form>

Spring gemの非デフォルト化

近年のPCハードウェア性能向上により、Springは新規Railsアプリケーションにデフォルトで含まれなくなりました。特別な理由がなければ、既存アプリケーションからも削除してよいでしょう。

Springを削除する場合

Gemfileからspringと関連するlistenspring-watcher-listenを削除します。

- gem 'spring'
- gem 'spring-watcher-listen', '~> 2.0.0'
- gem 'listen', '>= 3.0.5', '< 3.2' # springが依存していた場合

bin/railsbin/rakeからload File.expand_path("spring", __dir__)の行を削除し、config/spring.rbを削除します。

config/environments/test.rbではクラスをキャッシングする設定にします。

config.cache_classes = true

listen gemの削除に伴い、以下のファイル監視コードをconfig/environments/development.rbから削除します。

# config/environments/development.rb
config.file_watcher = ActiveSupport::EventedFileUpdateChecker

Springを使い続ける場合

springのバージョンが3.0.0未満だとエラーが発生するため、bundle update springでアップデートしてください。

undefined method `mechanism=' for ActiveSupport::Dependencies:Module (NoMethodError)

設定ファイルで、test環境のキャッシング設定を変更します。

# config/environments/test.rb
config.cache_classes = false
config.action_view.cache_template_loading = true

また、rails app:updateタスクでSpringをロードするコードをbin/railsとbin/rakeから削除してしまわないように気をつけましょう。

# bin/rails
load File.expand_path("spring", __dir__)

# bin/rake
load File.expand_path("spring", __dir__)

Sprocketsの使用継続または削除

Rails 7.0はsprockets-rails gemに依存しなくなりました。もしアセットパイプラインでSprocketsを使い続ける場合は、Gemfileに明示的に追加する必要があります。

# Gemfile
gem "sprockets-rails"

# Bash
bundle

使い続ける場合は、config/application.rbrequire "sprockets/railtie"config/initializers/assets.rbなどの関連設定を維持してください。逆にSprocketsを使わないのであれば、これらの設定は削除します。

# config/application.rb
require "sprockets/railtie"

# config/environments/development.rb
config.assets.debug = true
config.assets.quiet = true

# config/environments/production.rb
config.assets.css_compressor = :sass
config.assets.compile = false

# config/initializers/assets.rbの中身全て

Zeitwerkオートローダーの必須化

Rails 6.xでは従来のclassicモードも選択できましたが、Rails 7.0ではzeitwerkモードが必須となりました。そのため、config/application.rbでオートローダーを明示的に指定していたコードは不要になるので削除します。

# 以下の設定はどちらも不要
- config.autoloader = :classic
- config.autoloader = :zeitwerk

ハッシュ関数の変更に伴うクッキーローテーターの設置

セキュリティ向上のため、クッキーの署名などに使われるハッシュ関数がSHA1からSHA256に変更されました。

# config/initializers/new_framework_defaults_7_0.rb

Rails.application.config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA256

この設定を有効にすると、既存のSHA1で生成されたクッキーが読めなくなってしまいます。これを防ぐため、古いクッキーと新しいクッキーの両方を読めるようにする「ローテーター」をinitializerに設置します。

# config/initializers/cookie_rotator.rb
Rails.application.config.after_initialize do   Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
    authenticated_encrypted_cookie_salt = Rails.application.config.action_dispatch.authenticated_encrypted_cookie_salt
    signed_cookie_salt = Rails.application.config.action_dispatch.signed_cookie_salt

    secret_key_base = Rails.application.secret_key_base

    key_generator = ActiveSupport::KeyGenerator.new(
      secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA1
    )
    key_len = ActiveSupport::MessageEncryptor.key_len

    old_encrypted_secret = key_generator.generate_key(authenticated_encrypted_cookie_salt, key_len)
    old_signed_secret = key_generator.generate_key(signed_cookie_salt)

    cookies.rotate :encrypted, old_encrypted_secret
    cookies.rotate :signed, old_signed_secret
  end
end

ActiveSupport::Digest のダイジェストクラス変更

キャッシュキーの生成などに使われるActiveSupport::Digestのデフォルトダイジェストクラスも、SHA1からSHA256に変更されました。

# config/initializers/new_framework_defaults_7_0.rb

Rails.application.config.active_support.hash_digest_class = OpenSSL::Digest::SHA256

この設定を適用すると、既存のキャッシュは全て無効になります。デプロイ直後のパフォーマンスへの影響に注意してください。

スキーマダンプへのRailsバージョン記載

Rails 7.0から、db/schema.rbにスキーマを生成したRailsのバージョンが記録されるようになりました。これにより、バージョンの異なるスキーマが誤ってロードされるのを防ぎます。

# Rails 6.1
ActiveRecord::Schema.define(version: 2025_06_06_000000) do
  # ...
end

# Rails 7.0
ActiveRecord::Schema[7.0].define(version: 2025_06_06_000000) do
  # ...
end

stylesheet_link_tag のmedia属性

Rails 7.0では、stylesheet_link_tagヘルパーがmedia="screen"属性を自動で追加しなくなりました。

#config/initializers/new_framework_defaults_7_0.rb

Rails.application.config.action_view.apply_stylesheet_media_default = false

これまでは自動で付与されていましたが、今後は必要に応じて明示的に指定する必要があります。

# Rails6.1
stylesheet_link_tag 'application
=> <link rel="stylesheet" media="screen" href="/assets/application.css>

# Rails7.0では自動追加はなし
= stylesheet_link_tag 'application'
=> <link rel="stylesheet" href="/assets/application.css>

# Rails7.0では明示的に追加する
stylesheet_link_tag 'application',  media: 'screen'
=> <link rel="stylesheet" media="screen" href="/assets/application.css>

Cookie serializerフォーマットの変更

Rails 7.0では、クッキーのデフォルトserializerが:marshalから:jsonに変更されました。既存のアプリケーションを:jsonへ移行するには、一時的に:hybridモードを使用します。

# config/initializers/cookies_serializer.rb
Rails.application.config.action_dispatch.cookies_serializer = :hybrid

:hybridモードは、:marshal:json両方のフォーマットのクッキーを読み込めます。さらに、:marshal形式で書かれたクッキーを読み込んだ際に、自動的に:json形式で書き直してくれます。

十分な移行期間を経て、全ユーザーのクッキーがJSONに置き換わったと判断できたら、設定を:jsonに変更するか、設定ファイル自体を削除してデフォルト(:json)に準拠させましょう。

もし何らかの理由でJsonフォーマットに移行できない場合は、Marshalフォーマットを明示的に指定します。

# config/initializers/cookies_serializer.rb
Rails.application.config.action_dispatch.cookies_serializer = :marshal

逆に、6.1からすでにinitializerでjsonフォーマットを指定していた場合は、initializerを削除してしまってOKです。

# config/initializers/cookies_serializer.rb
Rails.application.config.action_dispatch.cookies_serializer = :json

git rm config/initializers/cookies_serializer.rb

Ruby 3.1/3.2エラー対策用Gemの削除

Rails 6.1とRuby 3.1もしくはRuby 3.2の組み合わせで起こるエラー対策として、net-smtpなどのGemをGemfileに追加していた場合、Rails 7.0では不要になるため削除してください。

# Gemfile
-gem 'net-smtp', require: false
-gem 'net-pop', require: false
-gem 'net-imap', require: false
-gem 'digest', '3.1.1', require: false

# Bash
bundle

4. 本番環境へのリリース

config.active_support.hash_digest_classの変更のように、一度適用するとロールバックが困難になる設定は、初回のリリースに含めないようにしましょう。

まず、安全な変更のみを適用してリリースし、数日間〜数週間運用して問題がないことを確認します。その後、ロールバックの難しい設定を個別に有効化し、再度リリースするという段階的な手法が最も安全です。

すべての設定を適用し終えたら、config/application.rbでアプリケーション全体のデフォルト設定を7.0に変更し、config/initializers/new_framework_defaults_7_0.rbは削除します。

# config/application.rb
config.load_defaults 7.0

# bash
git rm config/initializers/new_framework_defaults_7_0.rb

5. まとめ

Railsのメジャーバージョンアップは、アプリケーション全体に影響を及ぼす大きな変更です。開発段階では、新しいデフォルト設定を一つずつ適用し、丁寧な確認と修正が不可欠です。また、本番リリースも段階的に行い、万一の際に迅速にロールバックできるよう計画することで、ユーザーへの影響を最小限に抑えることができます。