Ruby on Rails 5.2から6.0へのアップグレード

Posted by Kohei Hayashi
on

先日Rails7.0がリリースされたことに伴い、バージョン5.2がセキュリティーパッチの対象から外れました。そのため、まだ5.2で動いているアプリケーションはセキュリティーリスク回避のために6.xバージョンへのなるべく早い移行が望まれます。
当社でも5.2を6.0にアップグレードする機会がありましたので、方法を記載しておきたいと思います。

GemfileでRailsとRubyのバージョン更新

以下のようにGemfileを更新します。

      
-ruby '2.6.3'
-gem 'rails', '~> 5.2.6'

+ruby '2.7.5'
+gem 'rails', '~> 6.0.0'
      
    

Rails6はrubyのバージョンが2.5.0以上の必要がありますので、それ未満だった場合は必要に応じてRubyのバージョンも更新します。今回のプロジェクトではRubyは2.6でしたのでRailsでは更新の必要はありませんでしたが、EOLが2022-03-31と近づいていたので2.7までアップグレードしています。
ローカル開発環境でのRubyアップグレードは、RVMやDockerなどを使って行います。
Gemfileの更新、及び新しいRubyのインストール後、bundle updateを実行し、新しいRailsをダウンロードします。

app:updateコマンドの実行

Railsにはアップグレード用のコマンドが用意されているので、基本的にはこのコマンドを実行して設定ファイルを更新していきます。 コマンドを実行すると各ファイルについて、Rails6の新しい設定ファイルで既存ファイルを上書きするか、既存ファイルをキープするか、マージするかなど、いくつか選択肢が表示されるのですが、マージを行うためには事前にマージツールをTHOR_MERGEという環境変数で指定しておく必要があります。 Ubuntuで行う場合、~/.profileファイルに以下のように記載します。私の場合はエディターにvimを使っているのでvimdiffを指定していますが、使っているエディターに合わせて自身が一番使いやすいものを設定します。

      
# ~/.profile
export THOR_MERGE="/usr/bin/vimdiff"
      
    
準備が出来たらrails app:updateコマンドを実行します。
このコマンドで更新・追加されるファイルには以下が含まれます(全てではないので、他にも対象ファイルが出てくるかもしれません)。
  • bin/setup
  • config/application.rb
  • config/environments/development.rb
  • config/environments/production.rb
  • config/environments/test.rb
  • config/initializers/content_security_policy.rb
  • config/initializers/new_framework_defaults_6_0.rb
  • config/locales/en.yml
  • config/puma.rb
  • config/routes.rb
  • config/spring.rb

各ファイル更新の際に選択肢が表示されますが、dを選択すると以前のファイルと6.0のデフォルトファイルとのdiffを確認出来ます。mを選択すると設定したマージツールが起動します。マージツールを使ってマージを行なう際は、config/environments/development.rbのようにオリジナルファイル名が表示されている方に保存を行ないます。config/environments/development.rb20220106-42674-d5roiz.rbのように表示されているものは一時ファイルのため、マージ作業が終わると消去されてしまいますので気をつけましょう。

もしコンフリクトが発生した場合は、以前のRailsバージョンの設定ファイルテンプレート(例:railties-5.2.6/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt)を参照すると良いと思います。もし削除されるコードが以前のバージョンのテンプレートファイル由来のものであれば、更新コマンドで上書きしてしまっても大丈夫でしょう。逆にもし削除対象のコードが自分が書いた設定ならば、おそらくバージョンアップ後もキープすべきです。

ホスト名設定

もし開発環境でlocalhost以外のカスタムドメインを使っている場合は、config/environments/development.rbで以下のように設定を追加します。

      
config.hosts << "your dev hostname"
      
    

CoffeeScript削除

もし以下のようなdepreciation warningが表示されてCoffeeScriptを必要としない場合は、Gemfileからcoffee-railsを削除してbundle installを実行すれば解消します。

      
DEPRECATION WARNING: Single arity template handlers are deprecated. Template handlers must
now accept two parameters, the view object and the source for the view object.
Change:
  >> Coffee::Rails::TemplateHandler.call(template)
To:
  >> Coffee::Rails::TemplateHandler.call(template, source)
 (called from <top (required)> at config/environment.rb:5)
      
    

routes.rbでの複数root

もしroutes.rbでサブドメイン対応などで複数のrootを設定していた場合、5.2では許可されていましたが6.0ではエラーになってしまいます。
解消するためには以下のように、それぞれのrootにユニークな名前を設定します。

      
root 'your_controller#method', as: 'unique_root_name'
      
    

routes.rbでのformatによる制限

もしroutes.rbでconstraintを使ってformat制限をしている場合、以下のようにlambdaに書き換えないと制限が効かなくなります。

      
# 5.2
resources :things, constraints: { format: [:html, :js] }

# 6.0 
resources :things, constraints: lambda { |req| ['html', 'js'].include? req.format }
      
    

ActiveRecord::Baseのupdate_attributesのdeprecation

次のバージョンのRails6.1ではActiveRecord::Persistenceupdate_attributes及びupdate_attributes!メソッドが無くなりますので、以下のようなdeprecation warningが表示されたら事前にupdate及びupdate!に書き換えておくのが良いでしょう。

CATION WARNING: update_attributes! is deprecated and will be removed from Rails 6.1 (please, use update! instead)

request.media_typeの使用

request.content_typeを使用していた場合、6.0からはメディアタイプにキャラクターセット情報が追加されますので、5.2と同様にメディアタイプ情報のみを取得したい場合はrequest.media_typeに変更します。

Zeitwerk

Rails6.0からはzeitwerkというオートローダーが採用されており、5.2以前のオートローダーはclassicモードと呼ばれるようになっています。まだ更新作業の途中段階ではconfig/application.rbのconfig.load_defaultsは5.2となっており5.2のclassicオートローダーが適用されてしまうため、以下のようにzeitwerkモードに切り替えます。

      
# config/application.rb
config.autoloader = :zeitwerk
      
    

Railsを再起動するとZeitwerkモードに切り替わりますので、rails zeitwerk:checkコマンドで問題がないかチェックします。
大抵の問題は、頭字語を変更することで解決出来ます。例えば、5.2ではvat_mailer.rbというファイルがあった場合、VATMailerというクラスを定義しても許可されていましたが、ZeiwerkではVatMailerのようにキャメルケースになっていないとエラーとなります。デフォルト動作を変更してVATMailerクラスを使い続けることも出来ますが、特別な理由がないのであれば素直にデフォルト設定に従いクラス名を変更した方が無難です。

また、concernディレクトリがオートロードパスに含まれることになったことが原因でエラーが起こることがあります。そのような場合は、以下のようにConcernネームスペースをモジュール定義と呼び出しから削除することで解決出来ます。

      
# 5.2 concerns/vat_decorator.rb
module Concerns::VatDecorator

# 5.2 呼び出し
include Concerns::VatDecorator

# 6.0 concerns/vat_decorator.rb
module VatDecorator

# 6.0 呼び出し
include VatDecorator

      
    

initializerを使った6.0デフォルト設定適用

この時点では、config/application.rbでconfig.load_defaults 5.2となっているため、6.0のコードベースに対して5.2のデフォルト設定が適用された状態でRailsが動作しています。
そこで、次は6.0のデフォルト設定を1つづつ試していくために、rails app:updateコマンドで生成されたconfig/initializers/new_framework_defaults_6_1.rbというファイルで各設定のコメントアウトを外し、動作に問題ないか確認します。但し、以下の2つの設定は一度適用すると、エラーが起こったときなどに5.2のコードベースに戻すことが難しくなってしまうため、他の設定を本番に適用して動作が安定していることを確認してから適用した方が安全です。1つ目はcookieに関する設定のため、クライアントブラウザーに保存されたcookieが5.2で解析出来なくなってしまうため、2つ目の設定ではメール送信の際に6.0にしか存在しないクラスを使うため、このクラスがActiveJobでredisに保存されたままで5.2に戻すとクラスが存在しないエラーとなるためです。(ちなみに5.2ではメール送信にActionMailer::DeliveryJobというクラスが使われていましたが、6.0ではdeprecateされています。6.1では完全に削除されるため、6.1移行前にredisに保存されているジョブが全て新しいクラスに置き換わっていることを確認する必要があります。)

      
Rails.application.config.action_dispatch.use_cookies_with_metadata = true
Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
      
    

自動テストが全てパスし、マニュアルテストでも動作が問題ないことを確認したら、本番にリリースします。しばらく運用し特に問題がないようであれば、上記2つの設定も本番適用します。その場合は、全てのデフォルト設定が6.0になり、initializerで設定を上書きする必要はなくなりますので、以下のようにconfig/application.rbでデフォルト設定を6.0にし、config/initializers/new_framework_defaults_6_1.rbは削除します。

config.load_defaults 6.0
    

以上で、Rails5.2から6.0へのアップグレードは完了となります。次回の記事では、6.0から6.1への移行も解説したいと思います。

---

弊社のRuby on Rails開発サービスにご興味がある方は お問い合わせフォーム からお問い合わせください。