🚀

Rails 6.1 → 7.0 アップグレードと Webpacker から jsbundling-rails + Webpack 移行

に公開

Rails 6.1.7 から 7.0.8.7 へのアップデートを行い、併せて Webpacker から sprockets-rails + jsbundling-rails + Webpack への移行も実施したため、その手順を記録しておきます。

参考:Rails アップグレードガイド – Railsガイド

前提

Ruby 3.1 から 3.2 へのアップグレード時のトラブルと対応策

Rails 7 にアップデートする前に、Ruby のバージョンを 3.2.6 に更新しました。

Railsバージョンアップ

Gemfile
- gem 'rails', '~> 6.1.7'
+ gem 'rails', '~> 7.0.8.7'
$ bundle update rails

アップデートタスク

$ rails app:update

差分を確認して必要な設定を戻します。
またrailsdiff.orgを参考にして、新しく追加された設定を確認します。

不要なファイルを削除

config/initializersの下記を削除。

application_controller_renderer.rb
backtrace_silencers.rb
cookies_serializer.rb
mime_types.rb
wrap_parameters.rb

参考:rails 7.0ではconfig/initializers配下のファイルが少なくなっている

Gemバージョンアップ

railsdiff.orgを参考にgemを更新。

Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '3.2.6'

- # Bundle edge Rails instead: gem "rails", github: "rails/rails"
+ # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main'
gem 'rails', '~> 7.0.8.7'
+ # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
+ gem 'sprockets-rails'
# Use postgresql as the database for Active Record
gem 'pg'
gem 'psych', '5.1.2'
# Use Puma as the app server
gem 'puma', '~> 6.5'
# Use SCSS for stylesheets
gem 'sassc-rails'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
- # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
- gem 'webpacker', '~> 5.4'
- # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
- gem 'turbolinks', '~> 5'
- # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
- gem 'jbuilder', '~> 2.13'
+ # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
+ # gem 'importmap-rails'
+ gem 'jsbundling-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', '~> 5.3'
- # Use Active Model has_secure_password
- # gem "bcrypt", "~> 3.1.7"

- # Use Active Storage variant
- # gem "image_processing", "~> 1.2"

+ # 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[mingw mswin x64_mingw jruby]

# Reduces boot times through caching; required in config/boot.rb
- gem 'bootsnap', '~> 1.18.3', require: false
+ gem 'bootsnap', require: false

+ # Use Sass to process CSS
+ # gem 'sassc-rails'

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

# Autoload dotenv in Rails.
gem 'dotenv-rails', '~> 2.8'

gem 'enum_help'

# Provides the generator settings required for Rails 3+ to use Slim
gem 'slim-rails', '~> 3.6'

# Configurable tool for writing clean and consistent Slim templates
gem 'slim_lint', '~> 0.27.0'

# A gem that provides a client interface for the Sentry error logger
gem 'sentry-rails', '~> 5.17'
gem 'sentry-ruby', '~> 5.20'
gem 'sentry-sidekiq', '~> 5.18'
gem 'stackprof'

# Simple, efficient background processing for Ruby.
gem 'sidekiq', '~> 6.5'

# Enables to set jobs to be run in specified time (using CRON notation)
gem 'sidekiq-cron', '~> 1.12'

gem 'faraday', '~> 1.10.3'

# A simple wrapper to send notifications to Slack webhooks.
gem 'slack-notifier'
gem 'slack-ruby-client', '~> 1.0.0'

# SAML toolkit for Ruby on Rails
gem 'ruby-saml', '~> 1.17'

# Banken provides a set of helpers which restricts what resources a given user is allowed to access.
gem 'banken', '~> 1.0', '>= 1.0.3'

# Salesforce
gem 'restforce', '~> 7.5.0'

# Ridgepole is a tool to manage DB schema. It defines DB schema using Rails DSL, and updates DB schema according to DSL.
gem 'ridgepole', '~> 2.0.3'

# Flexible authentication solution for Rails with Warden
gem 'devise', '~> 4.9'

# Easiest way to manage multi-environment settings in any ruby project or framework: Rails, Sinatra, Pandrino and others
gem 'config', '~> 5.5'

gem 'kaminari'

gem 'rails-i18n'

gem 'annotate'

# for google spread sheet
gem 'googleauth', '~> 0.17.1'
gem 'google_drive'

gem 'aws-sdk-s3'
gem 'mini_magick'

# jsRoute for path helper on JS
gem 'js-routes'

# Composite Primary Keys for ActiveRecords
- gem 'composite_primary_keys', '~> 13.0'
- gem 'tzinfo-data'
+ gem 'composite_primary_keys', '~> 14.0'

# Record versions
gem 'paper_trail'

gem 'net-imap', require: false
gem 'net-pop', require: false
gem 'net-smtp', require: false

group :development, :test do
- # Call "byebug" anywhere in the code to stop execution and get a debugger console
+ # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
+ gem 'debug', platforms: %i[mri mingw x64_mingw]
  gem 'awesome_print'
- gem 'byebug', platforms: %i[mri mingw x64_mingw]

  gem 'parallel_tests' # rspecとminitestのDBを分けるために利用. ローカル環境でのparallel実行は想定していない

  # Use RSpec
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  # Use RuboCop
  gem 'rubocop'
  gem 'rubocop-rails'
  gem 'rubocop-rspec'
  gem 'rubocop-performance'
  # Use Brakeman
  gem 'brakeman'

  # Use Pry
  gem 'pry-byebug'
  gem 'pry-doc'
  gem 'pry-rails'
  gem 'pry-stack_explorer'
end
gem 'faker' # NOTE: テスト以外でも使えるように

group :development do
- # Access an interactive console on exception pages or by calling "console" anywhere in the code.
+ # Use console on exceptions pages [https://github.com/rails/web-console]
+ gem 'web-console'

+ # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
+ # gem 'rack-mini-profiler'

+ # Speed up commands on slow machines / big apps [https://github.com/rails/spring]
+ # gem 'spring'

  gem 'listen', '>= 3.0.5', '< 3.10'
- gem 'web-console', '>= 3.3.0'

  # Use Better Errors
  gem 'better_errors'
  gem 'binding_of_caller'
  # Use Bullet
  gem 'bullet'
end

group :test do
- # Adds support for Capybara system testing and selenium driver
- gem 'capybara', '>= 2.15'
+ # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
+ gem 'capybara'
  gem 'selenium-webdriver'
  # This gem brings back assigns to your controller tests as well as assert_template to both controller and integration tests.
  gem 'rails-controller-testing' # TODO: 削除したい -> assigns( -> オミット後に消せそう

  # Use SimpleCov
  gem 'simplecov'
  # Use Ruby Tests Profiling Toolbox
  gem 'test-prof'
  # Use WebMock
  gem 'webmock'
end

gem 'google-cloud-bigquery'
gem 'nokogiri'
gem 'rack-cors'
$ bundle update

Rails 7.0 への移行で発生した問題とその解決策

プロジェクトで Rails 6.1.7 から 7.0.8.7 へアップデートした際に、Webpacker や DB 関連の問題など、いくつかの問題が発生しました。以下にそれらの問題と解決策についてまとめます。

1. Integer#to_s(:delimited) の非推奨化

発生した問題

Integer#to_s(:delimited) が Rails 7.0 で非推奨になり、以下の警告が発生しました。

DEPRECATION WARNING: Integer#to_s(:delimited) is deprecated. Please use Integer#to_fs(:delimited) instead.

解決策

to_s(:delimited)to_fs(:delimited) に変更。

- to_s(:delimited)
+ to_fs(:delimited)

参考:Deprecate to_s(format) in favor of to_formatted_s(format)

2. render './path' の非推奨化

発生した問題

render './api/v2/success' のように ./ を使ったパス指定が動作しなくなり、以下のエラーが発生しました。

ActionView::Template::Error: Missing partial ./api/v2/success

解決策

.// に変更。

- render './api/v2/success'
+ render '/api/v2/success'

参考:Deprecate rendering templates with . in the name

3. rails db:structure:load の廃止

発生した問題

rails db:structure:load を実行した際に、以下のエラーが発生しました。

rails aborted!
Don't know how to build task 'db:structure:load' (See the list of available tasks with `rails --tasks`)

Rails 7.0 から rails db:structure:load が廃止され、代わりに rails db:schema:load を使用する仕様になりました。

解決策

コマンドを以下のように変更。

- rails db:structure:load
+ rails db:schema:load

参考:Combine and deprecate rails db:structure:{dump,load} tasks into rails db:schema:{dump,load}

4. rails db:fixtures:load の外部キー制約エラー

発生した問題

bundle exec rails db:fixtures:load RAILS_ENV=test を実行した際に、以下のエラーが発生しました。

Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations.

Rails 7.0 からデフォルトで config.active_record.verify_foreign_keys_for_fixtures = true になり、外部キー制約に違反するデータがあるとエラーになる仕様に変更されました。

解決策

外部キー制約に違反するデータを特定し、Fixture ファイルを修正。

※ 暫定対応として、 config/application.rb に以下を追加も可。

config/application.rb
config.active_record.verify_foreign_keys_for_fixtures = false

参考:Verify foreign keys after loading fixtures

5. datetime 型の precision の挙動変更

発生した問題

Rails 7.0 では datetime 型の precision のデフォルト値が変更されました。
以前は無指定だと DB のデフォルトだったが、Rails 7 からは precision: 6 がデフォルトに、また nil を指定すると DB デフォルトになるという仕様になりました。
このため、ridgepole を使用している場合、スキーマ定義の見直しが必要になりました。

解決策

スキーマの定義を以下のように変更。

# before
t.datetime :column_a                 # DB のデフォルト precision
t.datetime :column_b, precision: 6

# after
t.datetime :column_a, precision: nil # DB のデフォルト precision
t.datetime :column_b                 # precision == 6(デフォルト)

参考:ridgepole/README.md

6. Turbo による影響と form_with の修正

Rails7では、Turbolinksに代わりデフォルトでturbo-railsというgemが用意されました。

発生した問題

Turbolinks から Turbo に変更した影響でボタンがクリックできない問題が発生しました。

form_withlocal: true は、フォーム送信時に JavaScript による Ajax リクエスト (XHR) を抑制し、通常の HTTP リクエストとして送信するためのオプションでした。しかし、Rails 7 では Turbo が導入され、Ajax リクエストの管理方法が変わったため、代わりに data: { turbo: false } を明示的に設定する必要がありました。

解決策

フォームの local: true オプションを data: { turbo: false } に置換。

- = form_with model: @user, local: true do |form|
+ = form_with model: @user, data: { turbo: false } do |form|

参考:Rails 7: local (non-XHR) request with a form_with form

7. CSP(コンテンツセキュリティポリシー)の変更

発生した問題

登録や変更ボタンをクリックするとエラーが発生しました。

Refused to execute inline event handler because it violates the following
 Content Security Policy directive: "script-src 'self' https: 'unsafe-inline'
  'nonce-607e9c9008282476644e0115c597a741'".
 Note that 'unsafe-inline' is ignored
  if either a hash or nonce value is present in the source list.

このエラーは CSP(Content Security Policy) によって script-src の制約が厳しく設定されているため、インラインスクリプト(onclick など)が実行できなくなっていることが原因です。

解決策

config/initializers/content_security_policy.rb:blob を追加することで解決。

config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
-  policy.script_src :self, :https, :unsafe_inline
+  policy.script_src :self, :https, :unsafe_inline, :blob
end

8. Sprockets::ArgumentErrorエラー

発生した問題

Rails 7 へアップデートした際に、Sprockets::ArgumentError: link_tree argument must be a directory というエラーが発生する場合があります。
このエラーは、app/assets/config/manifest.js に 存在しないディレクトリ を link_tree で参照していることが原因です。

Sprockets::ArgumentError: link_tree argument must be a directory

解決策

app/assets/config/manifest.js の該当コードを削除。

app/assets/config/manifest.js
- //= link_tree ../../../vendor/javascript .js

Sprockets の link_tree は、指定したディレクトリ以下のすべてのファイルをプリコンパイル対象にするため、誤ったパスを指定するとエラーになります。


JavaScript 管理の移行

Rails 6.1 から 7.0 へのアップグレードに伴い、JavaScript の管理方法を Webpacker から sprockets-rails + jsbundling-rails + Webpack に移行しました。

参考:jsbundling-rails/docs/switch_from_webpacker.md

なぜ Webpacker から移行したのか?

Webpacker の開発が終了し、Rails 7 ではデフォルトで JavaScript の管理に Importmap が採用されました。
しかし、Vue.js などのモダンな JavaScript フレームワークを使う場合は Importmap だけでは不十分なため、jsbundling-rails + Webpack を利用する形にしました。
また、Sprockets も引き続きアセット管理に使用することで、Rails の標準的なアセット管理と組み合わせる形にしました。

  • Sprockets (sprockets-rails) → CSS・画像・一部の JavaScript(非モジュール)を管理
  • jsbundling-rails + Webpack → モダンな JavaScript(Vue.js など)を管理

1. Webpacker等削除

Webpacker gemを削除。

Gemfile
- gem 'webpacker'

Webpackerパッケージを削除。

$ yarn remove @rails/webpacker webpack-dev-server

下記ファイルが不要になったので削除。

.browserslistrc
babel.config.js
bin/webpack
bin/webpack-dev-server
config/webpack/development.js
config/webpack/environment.js
config/webpack/production.js
config/webpack/staging.js
config/webpack/test.js
config/webpack/loaders/erb.js
config/webpack/loaders/vue.js
config/webpacker.yml
postcss.config.js
public/packs
public/packs-test

2. Webpackerのコンフィグを削除

config/initializers/assets.rb
- # Add Yarn node_modules folder to the asset load path.
- Rails.application.config.assets.paths << Rails.root.join('node_modules')
# config/initializers/content_security_policy.rb
- #   # If you are using webpack-dev-server then specify webpack-dev-server host
- #   policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?

3. jsbundling-rails の導入

インストール

$ bundle add jsbundling-rails
$ rails javascript:install:webpack

このコマンドにより、以下の設定が行われます。

  • bin/dev に Webpack を実行するスクリプトが追加される
  • package.json に Webpack の設定が追加される
  • app/javascript/application.js が作成される(ここに Vue.js などを import する)

4. packs 配下の Vue.js ファイルと application.js の移動

変更前(Webpacker 使用時の構成)

Webpacker を利用していた場合、Vue.js のエントリーポイント (application.js) や Vue コンポーネントは packs/ 配下に配置されていました。

app/javascript/
├── packs/
│   ├── application.js  # Webpacker のエントリーポイント
│   ├── components/  # Vue.js のコンポーネント
│   │   ├── app_message.vue
│   │   ├── app_header.vue
│   │   ├── app_footer.vue
│   │   └── ...
│   └── ...
└── ...

変更後(Webpack + Sprockets へ移行後の構成)

Webpacker を廃止し、Vue.js のエントリーポイントとコンポーネントを整理します。

app/javascript/
├── application.js  # メインエントリーポイント
├── components/  # Vue.js のコンポーネント(packs/components/ から移動)
│   ├── app_message.vue
│   ├── app_header.vue
│   ├── app_footer.vue
│   └── ...
└── ...

主な変更点

  1. app/javascript/packs/application.jsapp/javascript/application.js に移動
  2. app/javascript/packs/components/app/javascript/components に移動

5. webpackの設定

webpack.config.js
const path = require("path")
const webpack = require("webpack")
const { VueLoaderPlugin } = require("vue-loader")

module.exports = {
  mode: "development",
  devtool: "source-map",
  entry: {
    application: "./app/javascript/application.js",
  },
  output: {
    filename: "[name].js",
    sourceMapFilename: "[file].map",
    chunkFormat: "module",
    path: path.resolve(__dirname, "app/assets/builds"),
  },
  module: {
    rules: [
      { test: /\.vue$/, loader: "vue-loader" },
      { test: /\.scss$/, use: ["vue-style-loader", "css-loader", "sass-loader"] },
      { test: /\.erb$/, use: "rails-erb-loader" },
      { test: /\.css$/, use: ["vue-style-loader", "css-loader"] },
    ],
  },
  resolve: {
    alias: { components: path.resolve(__dirname, "app/javascript/components") },
    extensions: [".js", ".vue", ".json"],
    modules: ["node_modules", path.resolve(__dirname, "app/javascript")],
  },
  plugins: [
    new VueLoaderPlugin(),
    new webpack.EnvironmentPlugin(Object.keys(process.env)),
    // Webpack5ではprocessはundefinedになる()ので、processを使う場合は以下のプラグインを追加する
    new webpack.ProvidePlugin({ process: "process/browser" }),
    new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
  ],
}

参考:webpack でビルドするときの環境変数を読み込む方法の整理と、読み込み方法の切り替え

6. javascript_pack_tag を javascript_include_tag に変更

app/views/layouts/application.html.slim
- = javascript_pack_tag "application", "data-turbolinks-track": "reload"
+ = javascript_include_tag "application", "data-turbolinks-track": "reload"

まとめ

Rails 6.1.7 -> 7.0.8.7 へのアップデートでは、フレームワークの進化に伴ういくつかの変更に対応する必要がありました。

主なポイントとしては、新しいデフォルト設定の適用、非推奨機能の移行、依存関係の調整などが挙げられます。特に Webpacker から sprockets-rails + jsbundling-rails + Webpack への移行により、アセット管理の構成を見直し、一部のコードに修正が必要となりました。

アップデートを進める際は、変更点を理解しながら慎重に適用し、テストを通じて影響を確認することが重要でした。

Discussion