Ruby Rails 4 升级至 6 心得分享

kevinluo201 · 2021年02月16日 · 最后由 aaline57963 回复于 2021年02月18日 · 620 次阅读

Blog 版 https://dev.to/kevinluo201/rails-4-6-2iij

分享最近把专案从 Rails 4.2 硬生生地升级到 6.0 的心得。专案是公司内部用的系统。

TL;DR

Why?

其实呢,以一个内部系统而言,因为用的都是公司自己的员工,事实上似乎没什麽特别去升级的必要。

不过主要本公司为了要过某个 ISO 认证,软体这部分有个「安全性」要考量,许多 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 的测试都全补了。不过该公司在台北 + 德国有 100 多个工程师可以补测试。

第二次是帮某团队从 rails 4 升到 rails 5。因为真的不是说多熟 rails 百花撩乱的设定,我那时是採偷吃步的方式...用搬的:我直接 new 了一个 rails5 的专案,然后,假如我要搬某个 model ,我在新专案直接 rails g model再把旧的程式码一个个搬进去 xD

多年以后,我还是对 rails 升级不熟 xD 但以上两个方式都不适用了,我们只有 2 个人,coverage 只有不到 40%;另外程式码实在太多了,根本不可能用搬的,甚至有些 js 引用的方式异常神祕,我连怎麽搬都不知道。

所以这次採用的流程如下,当然中间我们还是持续开發新的功能,所以就是每个礼拜安插一些时间来处理,前前后后大概花了 5 个月吧~

  1. 引入 CI
  2. 修復既有 test
  3. 补上 test
  4. 移除不用的 Gem
  5. 整理 feature 清单
  6. 升级

引入 CI

就是懒的在本地跑 rails test CI 可是确保 branch 上的 test 全过的检查机制,没过绝不要 merge 到主线裡啊! 别让情况变的更糟

修復既有 test

好像没什麽需要补充的,不过花了非常多时间。

补上 test

虽然我们只有 2 个人,在短时间内把原来不到 40% 的测试要补到超过 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 才这样做。如果还要持续用的话,还是得想办法 merge 回 upstream

另外有些 Gem 根本就停止维护了,连 maintainer 都跑了,你丢 PR 也不理你的 xD

其实这件事我到现在也尚未做完,所以才为什麽只升到 6.0 而已 😅 因为 2020 年年底 6.1 已经出了 但因为已经把安全性的更新都完成了~ 就先这样

整理 feature 清单

因为整体 test coverage 最后其实还是不高,这样子就要硬上风险太大了。幸好到 2021 年时我们已经接独这个系统 8 个多月了,对于这个系统的功能,已大致了解完全。 大部分缺的是 controller 或 view 的 test, Rails 裡的确要写这两种 test 不是非常好写,

  • controller test:有些没设计好的 action 的 test 很难写,要准备大量资料。不好好准备资料的话,有时候甚至也是从头 mocking 到尾。
  • view test:无法对 partial 做 unit test ,那就变成跟 controller 一样要准备好资料。不过 6.1 有 view_component 未来应该可以增加 partial 的 unit test

不过,为了达到我们的需求,我改列了一个每个页面上所有功能的清单,来做手动测试。 其实没什麽祕诀,就是做一个非常长的 checking list,依序检查 我记在 Notion 上方便共同编辑,我刚大概滑了一下,大概滑了 20 秒才滑完,所以真的很长啊!

2 人大概花了 2~3 天才完成这个清单。 而且完整地测一次大概要 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 主要核心也是列出完整的 feature 让开發者或 QA 在上 production 前手动测试,看来逻辑还是正确囉

升级

前面都是在准备升级,现在才真的开始要升 升级时,直接切一个 branch 就开始做啦! 基本上是要参照前人的心得做: Upgrading Ruby on Rails — Ruby on Rails Guides 它的第一章 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

如果能重来...我会

  1. 绝对先把要移除的 gem 来移除掉,
  2. 如果还是要持续使用某些不支援新版本 rails 的 gem,先 fork 一份,但确保能 merge 回 upstream,否则对后来的同事来说,依然是一个没人维护的 gem。否则就是要自告奋勇去当 maintainer 了
  3. 补齐 test,Rails 有了 view_component的概念,我想 controller 的 html 回应应该比较好做 test,补好 controller 应该破 80% 的 test coverage 没什麽问题,也可以避免更多问题了
  4. 觉得无论如何都需要一份 Feature 清单,可以是列表、 Workflow 或 User Story Mapping 的形式。如果有资深的 QA,应该会本来就会做一份
  5. 最后上线后,出问题的地方是 cronjob ,上面讲的方法的确没特别关心 cronjob。cronjob 裡呼叫 async 的方法,如 deliver_later,因为没设定 config.active_job.queue_adapter = :sidekiq,信根本不会寄出去

大概就是这样,倒不是什麽教学,祝大家升级圆满

微服务的话,一个一个升级,风险会小很多。 还可以部署两个版本什么的。

但升级几个服务也是挺麻烦的事。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号