Railsバージョンアップ備忘録 5.1→5.2編

初めまして。株式会社[iCARE](https://www.icare.jpn.com/)のサーバーサイドエンジニアの[yotuba](https://twitter.com/yotuba_eng)です。
[前回の記事](https://qiita.com/yotuba088/items/71c8acdac58b80f38d96)ではRails5.0から5.1へのバージョンアップについて書きましたが、無事にリリースが出来ました。
今回の記事では、Railsの5.2へのバージョンアップについて備忘録を書きたいと思います。

現在弊社のCarelyというサービスは、長いこと、Railsの5.0.7.2で稼働していて、現在はRails5.1.7まで上がりましたが、今回5.2系へバージョンアップを弊社技術顧問の[willnet](https://twitter.com/netwillnet)さんと連携してやらせて頂いています。

今回の記事では、5.２系へバージョンアップを現在しようとしている際にぶつかった問題を共有させて頂きたいと思います。

## 5.2系へ上げた際に落ちたテストからわかる変更点

バージョンアップする際には、まず、こちらのRailsガイドの[リリースノート](https://railsguides.jp/5_２_release_notes.html)に書いてあるものなので、そちらを参考にして頂くのが一番良いと思います。

## まず
Railsのバージョンをあげる前に関連するgemをあげるべきだと色々な記事で書かれていますが、これを身をもって知ることになりました。
なんとbundle update railsでバージョンをあげようとすると、gemの依存関係にひっかかってしまって上がらないのです！(数年アップデートが止まってしまっていました・・・)
具体的には様々なgemの依存関係が
(3.0 < hoge)
とか
(5.1 > hoge)
のようにエラー文として吐き出されます。

Rails以外のgemを全て上げていきたいところですが、量が膨大なこともあり、上のエラー文を読み解いて必要なgemを上げていきます。

具体的には(5.1 > hoge)となっているようなgemはrailsや関連gemの5.1以下を指定していますので、アップデートする必要があります。

## つぎに
またgemの話になります。上記を解決してバージョンアップしたPRを作ることができました。
ただ、gemは大体の場合、RubyやRailsのどのバージョンをサポートしているかが書いてあります。
新しいRubyやRailsが出ると、gemを作ってくれてる人たちが新しいRubyやRailsで動くように修正してくれるわけですね。（更新が止まってしまっているgemを使わない方がいいというのは、こういった対応がされなくなるためでもあります）

gemのバージョンが古いままだと、Rails5.2をサポートしていなかったりします。gemのバージョンで何を変えたかはchangelogというファイルに書いてあり、そこにsupport rails 5.2などと書いてある場合は、それ未満のバージョンのままrails5.2にあげると問題が起きます。（起きたから修正したバージョンが出たわけです）

なので、bundle outdatedなどのコマンドで古くなっているgemを確認し、１個づつ上げていく必要があります。
これが非常に大変です。。。

具体的には[Ransack](https://github.com/activerecord-hackery/ransack)や[PublicActivity](https://github.com/chaps-io/public_activity)などがひっかかりました。

## 本題
gemの話が続いてしまったので、Railsのバージョンアップにより動かなくなってしまったコードについて解説します。

### ActiveModel::Dirtyのchanged?の挙動
```
DEPRECATION WARNING: The behavior of `attribute_changed?` inside of after callbacks will be changing in the next version of Rails. 
```
というDEPRECATION WARNINGが5.1で出ます。
これは何かというと、

```
class Model < AR::Base
  after_save :callback

  def callback
    p changed?
  end
end

model = Model.new
model.body = '変更した'
model.save
#=> trueが出力される
```

```
class Model < AR::Base
  def callback
    changed?
  end
end

model = Model.new
model.body = '変更した'
model.save
p model.callback #=> falseが出力される
```
上記二つのコードが5.1までは別の挙動をしていましたが、わかりづらいということで、5.2で両者の挙動は後者の挙動で統一されました。

[こちら](https://qiita.com/htz/items/56798d53ec5988733fc6)の記事にまとめて頂いている方がいますが、5.1の挙動を維持できるメソッドへ変更して解決しました。。

### callbackの際にfalseの場合、callbackチェーンを終了させない設定
callback処理でfalseをreturnしても、callback chainを終了させない設定を行うinitializerが、こちらもdeprecatedになり、削除して対応しました。

[こちら](https://y-yagi.tumblr.com/post/144067860535/rails-5%E3%81%A7%E8%BF%BD%E5%8A%A0%E3%81%95%E3%82%8C%E3%81%9Finitializer%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81)のブログに書いて頂いている方がいますので確認ください。

5.2からは、callback chainを終了させたい場合は、明示的にthrow :abortを使う必要があります。

### モデルのclass_nameに渡すのをStringにする
このようなエラーが出ました。

```
A class was passed to `:class_name` but we are expecting a string. (ArgumentError)
```


[こちら](https://github.com/rails/rails/commit/e65aff70696be52b46ebe57207ebd8bb2cfcdbb6)でマージされたものですが、単純な話で、モデルのclass_nameにStringを渡す必要があるので修正しました。

### モデルのCallbackの際に、if/unlessにStringを渡せなくなった。
このようなエラーが出ました。

```
ArgumentError: Passing string to be evaluated in :if and :unless conditional options is not supported. Pass a symbol for an instance method, or a lambda, proc or block, instead.
```

☓ if: 'first_name.blank?'
○ if: proc { |s| s.first_name.blank? }

Procで書くやり方に修正しました。


## 終わりに
今回もいかがでしたでしょうか。
実際に動いているプロダクトで起きた問題に絞って記事を書かせてもらいました。
長年gemのアップデートを放置していると大変なことになるというのが教訓です。。。みなさん気をつけてくださいね！