管理Rails模式和数据迁移的方法指南

200 阅读6分钟

随着时间的推移,你需要改变你的软件的数据模型。Rails让这一切变得非常容易,但却把许多日常过程留给你去解决(尖刀原则)。

一旦你过了一个项目的最初脚手架阶段,你就会对如何管理你的应用模式产生疑问,尤其是在团队工作的时候。

在你的过程中添加规则,可以将良好的实践编成法典;一个枯燥的检查表并不性感,但它可以完成工作,并将你解放出来,从事更有价值的工作。

除非你有强烈的、特定的理由不这样做,否则请遵循这些规则:

  • 只对模式变化使用迁移
  • 使用一次性的脚本来播种/导入数据
  • 积极地修剪脚本和同步环境

只对模式变化进行迁移

ActiveRecord Migration DSL是所有Web编程中最棒的工具包之一:它很容易编写迁移文件,并在本地进行测试,而且版本处理的设计也非常稳健。

由于迁移文件是Ruby类,所以你可以在其中放入任何类型的任意代码。你可以使用migrations DSL,执行原始SQL命令,或者使用你的应用程序代码创建记录。

但是你会发现有很多建议说你不应该在迁移中引用ActiveRecord模型:

在Ruby on Rails应用程序的生命周期中,你的应用程序的模型会发生巨大的变化,但根据Rails指南,你的迁移不应该发生变化。

"一般来说,编辑现有的迁移并不是一个好主意。如果现有的迁移版本已经在生产机器上运行过,那么你会给自己和同事们带来额外的工作,并且会引起很大的麻烦。相反,你应该写一个新的迁移,执行你所需要的变化"。

这意味着,如果你的迁移引用了你在app/models中定义的ActiveRecord模型对象,你的旧迁移很可能会中断。这可不是什么好事。

这个建议来自于good-migrations 的README--这是一个可以防止在迁移中加载你的模型的 gem,因此不可能让你自己陷入混乱。如果你不能加载你的模型,你就不能在迁移中意外地引用它们。

随着你的代码库的发展,Model.createModel.update 调用洒在旧的迁移中,使得你很难在没有错误的情况下对一个全新的数据库运行这些迁移程序。

你可以通过在迁移的范围内重新定义模型来解决这个问题,或者如果你做了一些破坏性的改动,你可以非常勤奋地重构旧的迁移,但是底线是:这不值得。

让你的迁移进入一个破碎的状态是一个巨大的混乱--你的团队中的每个人最终都可能拥有一个稍微不同的本地数据库,或者你的环境会因为迁移的顺序而失去同步。如果你做得不对,你的CI服务器将无法从头开始重建一个测试数据库。

通过避免这种情况来解决这个问题:只做模式迁移。如果你在迁移中看到迁移DSL以外的代码,不要合并它

用于播种/导入数据的一次性脚本

在一个数据量大的应用程序中,通常会在模式改变后立即导入或修改一堆数据。对于这种情况,可以编写一次性的脚本,然后用rails runner 。我建议把这些脚本放在db/script 文件夹中:

  • 编写一个脚本来创建你需要的数据(种子,从文件中导入,批量更新,等等)
  • 用你的本地数据库测试它
  • 部署到你的其他环境并运行该脚本
  • 一旦脚本在各处运行后,将其从源码控制中删除(在下一节中会有更多的内容)。

下面是一个例子:

class AddArticleCategories < ActiveRecord::Migration[6.0]
  def change
    create_table :article_categories do |t|
      t.string :name, unique: true, null: false
    end
  end
end
# Seed initial categories
rails = ArticleCategory.create!(name: "rails")
js = ArticleCategory.create!(name: "javascript")
git = ArticleCategory.create!(name: "git")

# Back-fill categories
Article.each do |a|
  if a.title.downcase.includes?("rails")
    a.update(category: rails)
  end
end

puts "Done!"
# Run locally
bin/rails runner db/script/seed_categories.rb

# Push script to staging and run migration
heroku run --app my-app-test bin/rails runner db/script/seed_categories.rb

# Push script to production and run migration
heroku run --app my-app bin/rails runner db/script/seed_categories.rb

现在脚本已经在生产环境中运行了,你可以从项目中删除它。

积极地修剪和同步环境

通过这种方法,我们将模式迁移视为持久的,而将数据脚本视为一次性的。一旦脚本在生产中被运行,它就不需要再存在了。

如果你需要再次引用它,请使用你的git 历史记录,而不是一个巨大的文件夹,里面装满了可能已经过时和损坏的脚本。而且在某些情况下,保留旧的脚本是有害的,因为有人可能在几周后不小心重新运行。

但是,如果你的团队中有人生病外出,没有让脚本在本地运行,现在它已经被删除了,怎么办?开始把你的数据库环境当做一条单行道。

  • 生产是黄金标准:生产中的任何东西都是真理的绝对来源
  • 你可以把生产环境下拉到暂存/测试环境中去
  • 你可以把暂存环境下拉到本地的开发数据库上

数据可以安全地从生产环境->暂存环境->开发环境流动,但绝对不能反过来。

如果有人需要得到最新的信息,让他们把暂存数据库克隆到他们的本地数据库。作为发布过程的一部分,定期将生产数据库克隆到暂存环境。

如果你在Heroku上,使用parity 来做这个。这是一个超级方便的方法,可以为你的项目添加单行 "复制这个环境到那个环境 "的功能

可选:清理旧的迁移

根据你的应用程序的复杂程度,你可能会发现自己被淹没在迁移文件中。把它们放在你的db/migrations 文件夹里是没有坏处的,但是如果你愿意,你可以把它们删除。

只要确保你在CI/测试时从db:create/db:migrate 的方式切换到db:schema:load 的方式。

和数据脚本一样,如果你需要参考一些旧的迁移,你可以通过源代码控制来实现。

何时偏离

在以下情况下,你需要采用不同的工作流程:

  • 你需要零停机时间的迁移
  • 你有敏感的数据,无法同步环境
  • 你有一个复杂的多数据库设置

...可能还有其他一些情况。正如他们所说。"但当然也有明显的例外......"

如果是这种情况,你应该偏离这些规则!

但是,在给你的流程增加额外的复杂性之前,请认真思考并挑战你是否真的有这些要求。你总是可以在以后根据需要增加能力,但一旦你变得依赖它们,就很难再把它们拿走。

总结

特别是当涉及到接触生产数据时,无聊是一种美德。我们不想要花哨的设置,因为如果(说实话:当......)它们坏了,我们就会进入一个高压力的情况。

想象一下,当你试图进行部署的时候,迁移失败了--或者模式改变成功了,但是在一些数据中出现了异常。现在你有一个问题。你要回滚吗?用SSH登录并重新运行脚本?开始在Rails控制台里瞎折腾?出一身冷汗?

如果你曾经花了一个小时试图帮助诊断为什么队友的本地数据库与你的数据库不匹配,或者为什么每次运行迁移时schema.rb ,你会从这个策略中受益。

如果你遵循这些规则:

  • 只对模式变化使用迁移
  • 使用一次性的脚本来播种/导入数据
  • 积极地修剪脚本和同步环境

你将会为可靠的数据库迁移做好准备,在你(和你的团队)应该如何处理变化的数据方面有明确的护栏。