Rails 4 升級至 6 心得分享
Tags: rails, chinese, upgrade
Published: 2021-02-16 04:18:56


分享最近把專案從 Rails 4.2 升級到 6.0的心得。專案是公司內部用的系統。

# TL;DR
* [Upgrading Ruby on Rails — Ruby on Rails Guides](https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html) 
細看 `General Adivce` 再根據要升級的版本去下面找對應的章節，即可升級

* [How to Upgrade Rails Without a Test Suite - FastRuby.io | Rails Upgrade Service](https://www.fastruby.io/blog/rails/upgrade/testing/how-to-upgrade-rails-without-tests.html) 
如果 test coverage 過低或者根本沒有寫自動化測試，可參考這篇文章的策略

# Why?
其實呢，以一個內部系統而言，因為用的都是公司自己的員工，事實上似乎沒什麼特別去升級的必要。

不過公司有個「安全性」的考量，而許多 Gem 存在安全性風險，而且在比較後面的版本都修復了，所以需要升級。但那些 Gem 的版本被 Rails 的版本卡住，以至於說需要將 Rails 升級。

舊版本的 Rails 也漸漸不再被維護，升級 Rails 除安全性外也可以有更好的開發體驗吧！或者說，使用開源框架就是要跟著社群一起成長啊！
也避免公家機關那種：蛤你們還在用 windows 95? 的這種情形

另個簡單的原因是：老闆就是要升級。這理由也夠充分了😅

# 專案介紹
我負責的專案是公司內部用的 CRM 系統，最早約 2013 年就開始第一版開始使用了。
根據 git 的情形來看，主要開發人/團隊至少換過 5 次甚至更多。
本公司是間純軟體公司，但「內部系統」不是正式賣給客戶的產品，狀態不佳可說是次等公民。
* Test coverage 差，也沒有引入 CI
* 後來都是外包，風格迥異
* 每個團隊都有自己愛用的 gem

# 過程
我們只有 2 個開發者，2020年4月才入職。因為一開始就知道升級 rails  是目標之一，所以滿早開始就在想如何做到了。

我只有兩次升級的經驗，
第一次是台北某德商公司，rails 3 升到 rails 4，採用的策略是把 test coverage 幾乎補到100%。真的很屌...連 end-to-end 的測試都全補了。不過該公司在台北+德國有一堆工程師可以補測試。

第二次是幫某團隊從 rails 4 升到 rails 5。因為真的不是說多熟 rails 百花撩亂的設定，我那時是採偷吃步的方式...用搬的：我直接 new 了一個 rails5 的專案，然後，假如我要搬某個 model ，我在新專案直接 `rails g model`再把舊的程式碼一個個搬進去 xD

多年以後，我還是對 rails 升級不熟xD
但以上兩個方式都不適用了，我們只有2個人，coverage只有30%多，補完 test 再升級可能老闆會凍未條；另外程式碼實在太多了，根本不可能用搬的，甚至有些 js 引用的方式異常神祕，我連怎麼搬都不知道。

所以這次採用的流程如下，當然中間我們還是持續開發新的功能，所以就是每個禮拜安插一些時間來處理，前前後後大概花了3個月吧~
1. 引入 CI
2. 修復既有 test
3. 補上 test
4. 移除不用的 Gem
5. 整理 feature 清單
6. 升級

## 引入 CI
~~就是懶的在本地跑 rails test~~ CI 可是確保 branch 上的 test 全過的檢查機制，沒過絕不要merge 到主線裡啊!別讓情況變的更糟

## 修復既有 test
好像沒什麼需要補充的，不過花了非常多時間。

## 補上 test
雖然我們只有 2 個人，在短時間內把測試補到超過 80% 幾乎有點不太可能，但還是須要做關鍵的 test 。那哪些算是關鍵的 test 呢？ 以我們的專案來說是：
* Model 的 Unit test
* Service Object 的 test
* 對外開放的 API 的 test

## 移除不用的 Gem
每個團隊有自己愛用的 gem ，舉例：有的人喜歡 httparty 、有的人喜歡 RestClient 、有的人喜歡 Faraday... 做人要有雅量，但寫程式實在不用那麼大的雅量... 儘量把同樣功能的 gem 改用同一個，否則一個 gem 就是一個 DSL ，到時全部 gem 一起跟著 rails 大升級，又要重看每個 DSL 的使用說明，很耗時間。

移不掉或必須的 gem，如果擋到升級，會先 fork 到自己的 github 修改再引用，之後再想看怎麼辦。

說是這樣說啦，其實應該還是打算移除，因為有些 Gem 根本就掛了，連 maintainer 都跑了，你丟 PR 也不理你的xD

其實這件事我到現在也尚未做完，所以才為什麼只升到 6.0 而已 😅 而不是 6.1
但因為已經把安全性的更新都完成了~ 就先這樣

## 整理 feature 清單
因為整體 test coverage 最後其實還是不高，這樣子就要硬上風險太大了。幸好到 2021年時我們已經接獨這個系統 8個多月了，對於這個系統的功能，已大致了解完全。

大部分缺的是 controller 或 view 的 test， Rails 裡的確要寫這兩種 test 不是非常好寫...

不過，為了達到我們的需求，我改列了一個每個頁面上所有功能的清單，來做手動測試。
其實沒什麼祕訣，就是做一個非常長的 checking list，依序檢查每頁的各項功能
我們記在 Notion 上方便共同編輯，我剛快速滑了一下，大概滑了20 秒才滑完，所以真的很長啊！

2人大概花了2天才完成這個清單。
而且完整地測一次大概要1天，所以測試很珍貴...
我們最後是測了 2 次，最後一次是在上線前。
真的滿累的，真心覺得 controller, view 的 auto test 不要省啊。
就算有 QA 也別累死 QA, 讓 QA 去找更有價值的 edge case 吧！

後來 ruby weekly 上有分享這篇文章
[How to Upgrade Rails Without a Test Suite - FastRuby.io | Rails Upgrade Service](https://www.fastruby.io/blog/rails/upgrade/testing/how-to-upgrade-rails-without-tests.html)
主要核心也是列出完整的 feature 讓開發者或QA在上 production 前手動測試，看來邏輯還是正確囉

## 升級
前面都是在準備升級，現在才真的開始要升
升級時，直接切一個 branch 就開始做啦！
基本上是要參照前人的心得做： [Upgrading Ruby on Rails — Ruby on Rails Guides](https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html)
**它的第一章 General Advice 非常重要，請務必詳讀**
基本上 Rails guide 裡把跳下個版本會遇到的事情都寫出來了，就不再贅述。
分享一些特別的調整：
### 4.2 -> 5.0
* Controller test 的 params 改為 keyword 形式
```
Deprecated style: get “/new”, { id: 1 }, { “X-Extra-Header” => “123” }
New keyword style: get “/new”, params: { id: 1 }, headers: { “X-Extra-Header” => “123” }
```
* environment config 要明寫 active_job 的 queue adapter
因為 4 還沒有用到 ActiveJob，我們都是直接 `include Sidekiq::Worker`
5 以後 action mailer 直接繼承 active job
```ruby
config.active_job.queue_adapter     = :sidekiq
```
### 5.0 -> 5.1
* Dirty 的方法名稱變了
```ruby
:changed 變成要選要 :will_change 或 :saved_changes
```

### 5.1 -> 5.2
* belongs_to 變成 required，如果要保持空值需特別加註 required: false。可以先關掉設定
`config.active_record.belongs_to_required_by_default = true`

### 5.2 -> 6.0
* autoloading 改成用 Zeitwerk。可以設定用原來的方式。
`config.autoloader = :classic`

# 結果
4 到 5 真的多超多東西，5 到 6 變得就沒那麼多，但應該速度變快了
升到 6 最有感的大概是 rails server 啟動的速度吧...本來快1分鐘，現在大概10秒
再來是 logger 的格式清楚許多，知道產出 SQL 是來自哪一行
也少了很多模棱兩可的寫法

更接近 modern 的 rails ，開發體驗越來越好囉~ 讚啦~

# 如果能重來...我會
1. 絕對先把要移除的 gem 來移除掉再升級，花非常多時間在處理 dependency
2. 如果還是要持續使用某些不支援新版本 rails 的 gem，先 fork 一份，但確保能 merge 回 upstream，否則對後來的同事來說，依然是一個沒人維護的 gem
3. 補齊  test，Rails有了 [view_component](https://viewcomponent.org/)的概念，我想 controller 的 html 回應應該比較好做 test，補好 controller 應該破 80% 的 test coverage 沒什麼問題，也可以避免更多問題了
4. 最後上線後，唯一出問題的地方是 cronjob ，上面講的方法的確沒特別關心 cronjob...


大概就是這樣，倒不是什麼教學，祝大家升級圓滿