25
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Rails5】今更ながらRails4からRails5へのアップグレードをどうやっていくのが良いか調べてみた

Last updated at Posted at 2019-03-21

はじめに

みなさん、こんにちは。今回はRailsアップグレードをどうやって行っていくのか、その手順や調べた文献を共有したいと思います。

Rails4からRails5にアップグレードします

現在、会社で運用しているRails4のシステムを今回Rails5にアップグレードします。
以前、先輩が行ったRailsアップグレードですが何らかの原因があってRevert・断念した失敗談があります。その後は、Rails4で新たな機能を作ってきた訳ですが、段々Rails4ではサポートしきれない問題が発生してきました。
特に問題だったのがフロントエンドの開発です。ここ最近は、弊社でもフロントエンドで行うこと、バックエンドで行うことをそれぞれ独立させて新機能を開発してきました。
特にフロントエンドの開発は、jQueryやcoffeeスクリプトをそろそろ止めて生JSで書くことが多くなってきました。
しかし、フロントエンドの開発においては、Railsのgemに依存するものが依然として残っており、このままRails4のまま開発していくのは辛みしかなかったので、アップグレードを急ぐこととなりました。
また、フロントエンドのテストシナリオにはRequest SpecSystem Specを利用する予定なため、今後の開発スピードを上げていくためにアップグレードを行うこととしました。

今回のアップデート

Rails 4.2.0 -> 5.0.7
ruby 2.3.6 -> 2.4.5

今回はrubyのversionがRails5でも必須バージョンよりも上なため、Railsだけをアップグレードします。
すみません。rubyも2.4系にアップグレードします。

アップグレードに参考にした文献

https://railsguides.jp/upgrading_ruby_on_rails.html#rails-4-2%E3%81%8B%E3%82%89rails-5-0%E3%81%B8%E3%81%AE%E3%82%A2%E3%83%83%E3%83%97%E3%82%B0%E3%83%AC%E3%83%BC%E3%83%89
https://techracho.bpsinc.jp/hachi8833/2018_11_14/64279

基本的には、前回RevertされたPRから、秘伝のタレのような修正がありますので、今回はほぼそれを参考にしています。上二つはRailsアップグレードを行うためにはどうすればいいのか、大まかな所を調べるために読みました。

アップグレード手順

  1. bundle updateによるコンフリクトを解消する
  2. app updateによるアップデートタスクを叩く
  3. ActiveRecord::Base継承の全モデルをApplicationRecord継承に置き換える
  4. DEPRECATION WARNINGの解消
  5. その他
  6. rubyの更新

bundle updateによるコンフリクトを解消する

まずは単純にRailsのversionをあげてみましょう。

Gemfile
- gem 'rails', '4.2.0'
+ gem 'rails', '~> 5.0.0'
% bundle update rails
Bundler could not find compatible versions for gem "rails":
  In Gemfile:
    rails (~> 5.0.0)

    ******* was resolved to *****, which depends on
      rails (~> 4.0)

railsをupdateしようと試みると、他のgemのrailsのバージョンとぶつかってしまいました。
~>によるバージョン指定は4.xの更新は上がってもいいが、メジャーバージョンが上がると利用不可という指定になります。
そのため、rails ~> 4.0を指定しているgemも更新が必要となります。このように、地道にRailsアプリで利用している全てのgemが競合しないように更新をかけていくのが最初の登竜門です。

Gemfileのバージョンの記述方法とその内容は以下の通りです。
http://railsdoc.com/references/gemfile

バージョン 説明
x.x.x バージョン固定
>= x.x.x x.x.x以上のバージョンが必要
>= x.x.x, < y.y.y x.x.x以上、y.y.y以下のバージョンが必要
~> x.0 x.1からx.9は良いが、メジャーバージョンが上がると不可

この辺りは以前のPRで大体差分は分かっているのですが、実際やっている事は地道にアップデートを繰り返す泥臭い苦行に入るので、説明はこれくらいにしておきます。

コンフリクトの解消が解決できたら、bundle updateをかけます。

% bundle update

app updateによるアップデートタスクを叩く

Railsのバージョン更新後はタスクとして提供されているrails app:updateを叩きます。これにより、新しいバージョンでのファイル作成や既存ファイルの変更を対話形式で行うことができます。

% rails app:update
    conflict  config/boot.rb
Overwrite /usr/local/rails/config/boot.rb? (enter "h" for help) [Ynaqdh]

コンフリクトが発生した時、上書きする場合はYを、更新せず既存の設定を保つ時はnを入力します。diffを確認したい時は、dを入力します。
差分を柔軟に設定したいときはqコマンドでrails app:updateで一時中断し、手動で該当ファイルを修正します(本当は、FreeBSDのmergemasterのように、エディタを開いて該当ファイルを直接修正するようなやり方があればいいのですが、app:updateタスクにはそのような機能はありませんでした...)

ActiveRecord::Base継承の全モデルをApplicationRecord継承に置き換える

Rails5.0以降では全モデルがApplicationRecordをスーパークラスとして使うようになりました。という事で、、全モデルから継承されるスーパークラスのモデルとなるapp/models/application_record.rb を作成します。

app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

あとは他のモデルをApplicationRecordから継承されるように正規表現で置き換えを行います。

./app/models配下のrubyファイル(ただし、スーパークラスとなるapplication_record.rbは覗く)を全てApplicationrecordから継承されるように置き換えを行います。

% find ./app/models/ -name "*.rb" ! -name "application_record.rb" -type f -print | xargs sed -i "s/ActiveRecord::Base/ApplicationRecord/g

DEPRECATION WARNINGの解消

Rails5および5.1以降でDEPRECATIONになるコードや設定を正規表現で置き換えを行い、修正をかけます。

FactoryBotへの置き換え

DEPRECATION WARNING: The factory_girl gem is deprecated. Please upgrade to factory_bot. See https://github.com/thoughtbot/factory_bot/blob/v4.9.0/UPGRADE_FROM_FACTORY_GIRL.md for further instructions. (called from require at /usr/local/lib/ruby/site_ruby/2.3.0/bundler/runtime.rb:81)
no_proxy is unsupported

下記のURLでFactoryGirl、factory_girlをFactoryBot、factory_botに置き換えるための正規表現のサンプルが載っているのでこちらを使います。

https://github.com/thoughtbot/factory_bot/blob/v4.9.0/UPGRADE_FROM_FACTORY_GIRL.md

grep -e FactoryGirl **/*.rake **/*.rb -l | xargs sed -i  "s|FactoryGirl|FactoryBot|"
grep -e factory_girl **/*.rake **/*.rb -l | xargs sed -i  "s|factory_girl|factory_bot|"

RSpecの修正

FactoryBotの静的属性の定義がdeprecateになりました。こちらはFactoryBot 5で削除される予定です。これからは、動的属性になるようにRSpecの記述をブロック形式にする必要があります。

https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/AttributeDefinedStatically

To automatically update from static attributes to dynamic ones,
install rubocop-rspec and run:

rubocop \
  --require rubocop-rspec \
  --only FactoryBot/AttributeDefinedStatically \
  --auto-correct

Gemfileにrubocop-rspecを追加して全RSpecファイルを修正します。

Gemfile
gem "rubocop-rspec"
% bundle exec rubocop \
  --require rubocop-rspec \
  --only FactoryBot/AttributeDefinedStatically \
  --auto-correct

before_skip_filter, before_filterをbefore_actionに変更

Rails5.1以降skip_before_filter,before_filterがdeprecateされるので、今のうちに削除します。

DEPRECATION WARNING: skip_before_filter is deprecated and will be removed in Rails 5.1. Use skip_before_action instead.

before_action has not been definedの解消

Rails 5.0以降、Deviseをインストールした事により使えるヘルパーメソッドなど、コントローラには直接定義されていないbefore_actionを呼び出そうとするとArgumentErrorを発生させてしまいます。

raiseによる例外処理でfalseを返すようにしてあげましょう。

before_action :authenticate_user!, raise: false

skip_callbackの置き換え

https://github.com/thoughtbot/factory_bot/issues/931#issuecomment-307542965

Rails4で使えたFoo.skip_callback(:create, :before, :sample_check)がRails5では使えません。
そこで、以下のように置き換えます。

  after(:build) do
    Sample.skip_callback(:create, :before, :example_check, raise: false)
  end

raise: falseのオプションにより、skip_callbackの機能を維持しながらエラーメッセージを回避することができます(というかRails4の時点でもfactoryのプログラムの中にあるskip_callbackが機能していなかったぽいですね)。

Rails4のスタイルをRails5のスタイルに変更する

Deprecated style:
get :show, { id: 1 }, nil, { notice: "This is a flash message" }

New keyword style:
get :show, params: { id: 1 }, flash: { notice: "This is a flash message" },

こちらのgetやpostリクエストのparams指定、flash指定もRails5から明示的に名前を入れるようなスタイルに変更になりました。
数が多いのでgemに頼ります。
https://github.com/tjgrathwell/rails5-spec-converter

Gemfile
gem "rails5-spec-converter"
% bundle exec rails5-spec-converter

xhrの記述を修正

DEPRECATION WARNING: `xhr` and `xml_http_request` are deprecated and will be removed in Rails 5.1.

xhrの記述も変更になったので、以下のように変更します。こちらは、 rails5-spec-converterで機械的に修正できないため手動で修正します。

- subject { xhr :get, :favorite }
+ subject { get :favorite, xhr: true }

uniqをdistinctに変更

DEPRECATION WARNING: uniq is deprecated and will be removed from Rails 5.1 (use distinct instead)

Railsのuniqメソッドがdeprecatedになったので、distinctに置き換えます。

deviseの更新

https://github.com/plataformatec/devise/issues/4171

上記のエラーに引っかかったため、deviseの更新を行います。

deviseのparameter展開を修正

DEPRECATION WARNING: [Devise] Changing the sanitized parameters through "Devise::ParameterSanitizer#for(sign_up) is deprecated and it will be removed from Devise 4.1.
Please use the `permit` method:

deviseのdeprecation warningも上述の通りに修正します。

また、deviseが更新されるにあたってDeviseコントローラ側でもCSRFの対策が必須となりました。
が、当社ではapplication_controller側で既にCSRFトークンを発行する設定を行なっているので、deviseライブラリの方でまたCSRF対策する必要がなかったため、 protect_from_forgery prepend: trueをdeviseコントローラをオーバライドしているコントローラに追記します。これにより、protect_form_forgeryコールバックがtrueを返せるため、deviseコントローラ側ではCSRF対策を防止することができます。

app/controllers/example/sessions_controller.rb

  protect_from_forgery prepend: true

だんだん疲れてきました。

redirect_to :backを置き換え

redirect_to :backもRails 5.1以降使えなくなるため、以下のように修正します。

こちらも同じように修正します。

- redirect_to(:back)
+ redirect_back(fallback_location: root_path)

レスポンスボディが空なものはhead

DEPRECATION WARNING: `:nothing` option is deprecated and will be removed in Rails 5.1. Use `head` method to respond with empty response body.

renderのnothing: trueが5.1以降使えなくなるので、代わりにheadメソッドを使うようにします。

- render nothing: true
+ head :no_content

findを使うときは必ずidを指定してあげる

DEPRECATION WARNING: You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`.

Rails5以降ではなく4.2から出る警告になりますが、5.1からArgumentErrorになるため、これも修正します。

- ***** = Sample.find(tmp_*****)
+ ***** = Sample.find(tmp_*****.id)

enumの返り値がキーになったので修正

 social_type
=> "social_type_sns"

enumでマッピングの値の方ではなく、キー名が返ってくるようになったので、これをRails4の時のように値を返すようにメソッド名を変更します。

social_type_before_type_cast
=> 1234

belongs_toで関連付けしている時はnullなデータが入るのを許可しないようになったので、optional: trueで回避

そもそも、belongs_toで紐づけられている子エンティティが親エンティティを参照できていないのが問題な気がしますが、ひとまず、optional: trueで回避します。

RailsでArrayのメソッドが直接使えなくなったので、配列変換

http://kamina-dev.hatenablog.com/entry/2018/01/17/175830

4.2 までは ActiveRecord::Relation の #method_missing で一部メソッドを Arrayに delegate していた
5.0 からはその処理が削除され、その代わりに Enumerable を include するように修正された
そのため、ほとんどのメソッドはそのまま使えるが、 #slice は Enumerable に存在しないため、使えなくなった

unshiftもsliceと同じく、使えなくなったため、コード的にどうかと思いますが、一先ずActiveRecord経由で取得したレコードを配列変換して解決します。

Admin.where(id: 100).to_a.unshift(current_user)

render_templateなどのSpecテストはひとまずコメントアウト

Rails5になったに伴い、controller_specで書いていたものはrequest_specに書くように推奨されています。
今後のcontroller_specでは、assignsとassert_templateの使用が非推奨となってる模様です。
直ぐに用意できないのであればひとまずコメントアウトで凌ぎましょう。

その他

その他はプロジェクト毎に必要・不必要なものがありますので、Optionalとして呼んでください。

XML シリアライズ

今後もXMLシリアライズを使うには、Gemfileにgem 'activemodel-serializers-xml'を追加します。

Railsテストのヘルパーメソッド用のgemを追加

https://railsguides.jp/upgrading_ruby_on_rails.html#rails-%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%A9%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88

assignsメソッドとassert_templateメソッドがrails-controller-testing gemに移転したため、これらを今後も使う場合はGemfileに以下を追加します。

Gemfile
gem "rails-controller-testing"

draperの更新

Draperはrails/test_unit/sub_test_taskのライブラリをrequireしていますが、Rails5以降はrailties/lib/rails/test_unitにいくつかのライブラリが移動しました。Draperが新しいライブラリをサポートできるようにDraperも3以上に更新します。

https://github.com/drapergem/draper/issues/681

image_tagがnilを許容できなくなったのでpresent?メソッドで回避

https://stackoverflow.com/questions/37530024/rails-5-nil-is-not-a-valid-asset-source

present?メソッドでActionView::Template::Error:を回避します。

where_valuesをorメソッドで置き換え

where_valuesメソッドが廃止となりました。
代わりにRails5から追加されたorメソッドを使います。
https://blog.bigbinary.com/2016/05/30/rails-5-adds-or-support-in-active-record.html

Rails5のproduction環境でのAutoloadが無効になったので、再度有効化する

Rails5ではデフォルトでAutoLoadがdisabledになったため、config/application.rbで有効にしておきます。

config/application.rb
config.enable_dependency_loading = true

websocket_railsがRails5で動くようにする

Rails5以降において、websocketによる双方向通信を実現するためには、ActionCable が簡単です。しかし、本システムでは既にwebsocket_railsによってwebsocketによる双方向通信の機能を実装しているため、ActionCableによる実装を行うまでは、websocket_railsが動くようにします。

ActionCableの概要

websocket_rails

基本的にGemのversion関連による不具合が多かったため、下記の記事を参考にgemのversionを固定しておきます。

第34回 あのGemは今 WebSocket編 (松永紘)

Sprockets 3以降、コンパイルしていないCSS、画像などのリソースファイルがapplication.assetsで読み込めなくなった

https://github.com/rails/sprockets-rails/issues/425

最初は、compass-railsによる参照で読み込めていたのですが、読み込んだCSSファイルが改行されず、タグが連結されて設定されるようになっていました。苦肉の策として今回直接参照しているファイルもprecompile対象とすることにしました。

rubyの更新

rubyを2.3から2.4にアップグレードします。今回はdeprecationが出ていた部分が外部アプリケーションのAPIレベルでしかなかったので、Rails側で特に何かすることはありませんでした。
しかし、2.4以降xmlrpcが標準ライブラリから外れてしまったので、こちらを追加しておきます。

Gemfile
gem 'xmlrpc'

https://stackoverflow.com/questions/41784012/ruby-fog-gem-causing-server-not-to-run-cannot-load-such-file-xmlrpc-client

また、FixnumがIntegerのエイリアスになったため、関係するGemも一緒に更新します。

https://stackoverflow.com/questions/41463999/warning-constant-fixnum-is-deprecated-when-generating-new-model

key must by 32 bytes エラーの解消

ruby 2.4以降openssl周りので変更があり、暗号、復号化に利用するキー文字列はきっちり32byteでないといけません。
https://qiita.com/rentalname@github/items/05eaf0afd9b8acf8f26d

Rails側で文字列を丸めてあげましょう。

ActiveSupport::MessageEncryptor.new(SECURE[0..31], CIPHER)

まとめ

ということで、Railsアップグレードの際にやったことを殴り書きですが、まとめてみました。実際にアップグレードを行った感想ですが、正直言って二度とやりたくないです←
何故なら、このようなフレームワークのアップグレードで必要なスキルはほぼリサーチ能力くらいだと思っているからです。また、ここ最近の筆者のモチベーションとして言語やフレームワーク、ツールの勉強をするよりも(RailsエンジニアやAWSやGCPと言ったクラウドエンジニアになりたい訳じゃない)、モノの考え方を重視してアーキテクチャの構造に関する知識やソフトウェアの設計能力を身に着けることが重要だと考えているので、「正直、一回はこういう経験するのは良いけどこれっきりで良いかな」とか思ってたりもします。
とは言え、何か問題が発生した時に落ち着いて対処する能力も重要だと考えているので、そういう意味では一回こういう経験をした良かったかなとは思っています。

上手くまとまっていない記事になっているかもしれませんが、ちょくちょく修正かけて良い記事にしていきます。それでは。

参考文献

25
19
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
25
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?