在FastRuby.io,我们建议使用双启动技术进行升级。这需要我们生成一个Gemfile.next.lock 文件,用于用下一个版本或Rails启动应用程序。在这篇文章中,我们将分享生成该文件的两种技术:更快的一种和更安全的一种。
更快的方法
这是最简单的一种。其步骤是:
- 将
next?的条件语句添加到Gemfile - 创建
Gemfile.next符号链接 - 运行
next bundle install(或next bundle update)
如果你不使用
next_rails,你可以运行BUNDLE_GEMFILE=Gemfile.next bundle install来代替
这个过程将从头开始生成一个新的Gemfile.next.lock 文件。它将让Bundler根据Gemfile中定义的限制来解决Rails下一版本所需的任何依赖关系,它将使用符合要求的每个gem的最新版本。
这种方法有一个问题,很多额外的宝石将与Rails及其依赖关系一起被更新,所以你不仅在升级Rails,还在升级与升级本身无关的任何其他宝石。如果升级的宝石碰巧引入了需要额外工作的变化,那么升级更多的东西就会导致更多的失败。
优点
- 简单快速,一个命令就可以创建新文件。
缺点
- 其他宝石会被更新,而不仅仅是升级所需的宝石,更多的变化==更多的东西会被破坏。
更安全的方法
这可能有点难做(但并不总是这样),但它能确保只有与升级有关的宝石被更新,而没有其他东西。那些其他的宝石可以在其他时间更新。步骤是:
- 将
next?的条件添加到Gemfile - 创建
Gemfile.next符号链接 - 复制原始的
Gemfile.lock,作为Gemfile.next.lock(注意这是一个副本,不是一个符号链接) - 运行
next bundle update rails(注意我们只告诉Bundler先更新rails) - 在这一点上,命令可能会因为试图解决一个依赖版本的问题而失败。
- 检查失败的原因,如果需要的话,更新你的Gemfile,并将需要更新的依赖关系的名称附加到
bundle update命令中。 - 重复步骤5和6,直到所有的依赖关系都被更新。
这个过程将生成一个Gemfile.next.lock 文件,尽量保持原版本的宝石,只更新 Rails 升级严格需要的宝石。我们必须帮助Bundler来决定哪些宝石应该被更新。
如果Bundler无法解决依赖关系,这种方法可能会花费更多时间,需要更多的人工输入,但结果是更保守的。我们已经看到,在使用更快的方法时,升级过程中的许多故障是由不需要的宝石更新引入的。这种更安全的技术在一开始会花费一些额外的时间,但它有可能节省数小时的调试时间。
优点
- 防止因不需要的宝石更新而引入故障
缺点
- 它可能需要手动检查错误和手动输入来协助Bundler
一个例子
让我们看一个例子,使用一个非常老的代码提交,从Rails 5.2开始,与Rails 6.0双启动,为Points生成一个Gemfile.next.lock 文件。
这就是当时的Gemfile。
现在让我们大致比较一下这两种方法对Gemfile.next.lock 所做的修改
原始Gemfile.lock 和使用保守方法的Gemfile.next.lock 之间的差异。
使用快速方法的原始Gemfile.lock 和Gemfile.next.lock 之间的差异。
我们可以看到,即使在文件的一个很小的部分,快速方法也比安全方法包括了更多的宝石的升级,即使是对database_cleaner 的一个主要版本跳跃。我们可以比较每张图片右边显示的差异,看看第二个差异的红色和绿色有多大。
修复Bundler冲突
根据你的Gemfile中列出的依赖关系,试图只升级rails gem可能会产生冲突,因为Bundler在可能的情况下只更新rails 和它的依赖关系,但它不会更新其他可能依赖相同依赖关系的不同版本的gems。
当我们遇到这些错误时,我们必须从上到下分析Bundler报告的每个冲突的原因。
例如,在一个从Rails 3.2升级到4.0的不同应用中,当运行next bundle update rails 命令时,Bundler显示了这样的错误信息。
You have requested:
capistrano ~> 2.15.4
The bundle currently has capistrano locked at 2.13.5.
Try running `bundle update capistrano`
If you are updating multiple gems in your Gemfile at once,
try passing them all to `bundle update`
在这个应用程序中,Gemfile 有一个条件,即使用不同的capistrano 版本。
if next?
gem 'capistrano', '~> 2.15.4'
else
gem 'capistrano', '~> 2.13.5'
end
所以我们必须将capistrano 列入要升级的宝石列表中。
next bundle update rails capistrano
在修复了这个问题后,可能会出现新的冲突。必须分析失败的原因,才能知道哪种宝石升级可以修复它。在某些情况下,可能需要更新Gemfile ,以使用不同版本的宝石(例如,如果我们有一个版本限制,对新的Rails版本来说过于严格,所以我们需要一个条件,为下一个版本放松限制)。然后,在确定了 gem 之后,我们运行bundle update rails capistrano ... 命令。
每添加一个 gem 到列表中都应该减少冲突的数量,直到bundle update 命令成功完成。
结论
我们建议使用保守的方法,特别是在有大量依赖关系的应用程序中。但你可以在使用Points的例子中看到,即使是依赖关系不多的相当小的项目,更新宝石的数量也是需要考虑的。
很难分享一个衡量标准,即更新所有可能的宝石会产生多少问题,以及与做保守的宝石更新的时间相比,因为它取决于太多的东西(项目的大小、依赖的数量及其依赖性、代码的复杂程度等等)。但是,根据我们的经验,调试失败的时间要比花时间帮助Bundler解决依赖关系的时间要长得多。
另外,解决依赖关系的过程是直接的(分析Bundler的错误信息,更新Gemfile,并在bundle update 命令中附加一个新的gem),而解决失败的过程对每个失败都会有所不同。