使用随时间变化的模型为 Sequelize 制作可重现的迁移字符串?
Posted
技术标签:
【中文标题】使用随时间变化的模型为 Sequelize 制作可重现的迁移字符串?【英文标题】:Making reproducible string of migrations for Sequelize with models changing over time? 【发布时间】:2017-10-17 12:02:45 【问题描述】:我正在使用 Umzug 和 Sequelize 构建我的第一个迁移系统,用于在后端使用 sequelize 和 express 的节点应用程序。
我的问题是,随着时间的推移,后端模型随着提交而发生变化(模型被删除、一些变化、一些被添加),这会破坏碰巧使用来自 sequelize 的模型的旧迁移。示例:
假设我有一个处理“UserStats”的迁移#1。五个版本之后的模型 UserStats 需要从应用程序中删除,因此模型被删除并进行新的迁移以删除表。
现在尝试启动新的开发环境会中断,因为当新服务器尝试运行所有旧迁移时,它会尝试为第一次迁移找到模型 UserStats,但该模型不再存在。
所以根本问题是模型的版本与应用迁移状态不同步。每次迁移都要求 sequelize 模型看起来像最初创建迁移时所做的那样。处理这个问题的最佳方法是什么?
【问题讨论】:
【参考方案1】:这里有两个值得了解的想法。两者的灵感都来自于我在 Ruby on Rails 中的背景,所以我包含了一些关于 Rails 是如何处理上下文的:
-
在Rails 中,当您设置一个新数据库时,它并不期望每次都运行一次迁移来确保数据库正确。 Rails 还维护一个名为
schema.rb
的文件,该文件保存数据库的当前、完整、最新状态(包括为达到该点而运行的迁移名称的列表)。因此,当您创建一个新数据库时,它只会读取 schema.rb
文件中的所有内容并创建这些表。
然后,rails 将在该数据库上运行 FUTURE 迁移,继续前进。但是在创建最新的schema.rb
之前运行的那些 - 好吧,你根本不需要它们。事实上,一旦它们在任何需要的地方运行,您就可以根据需要删除它们。
现在,sequelize 不会这样做(尽管我希望它这样做!)。但是,您可以经常将整个数据库结构转储为 SQL。将其保存为例如。 20170427051240-initial-structure.sql
,在您的迁移文件中,时间戳记为它包含的最新迁移之后的一秒。所以,现在你有了类似 Rail 的scehma.rb
。
下一步是:编辑直接在该时间戳之前运行的迁移,以便它所做的只是导入整个数据库结构。像这样的:
'use strict';
module.exports =
up: function(queryInterface, Sequelize)
if (!['test', 'ci'].includes(process.env.NODE_ENV))
// In all other environments, the database will already have been set up, so
// we don't need to import the full structure
return;
return queryInterface.sequelize.query(`
BIG FAT STRING OF SQL GOES HERE.
Note: in my app I've actually got it done table by table,
in separate chained promises, but don't remember why.
`)
好的。因此,现在您可以删除该迁移之前的所有迁移 - 因为无论如何该迁移都会处理之前运行的所有迁移。
有一些注意事项,例如。你可能想保存20170427051240-initial-structure.sql
,然后等待一两个月,让另外15个迁移累积,然后执行上述步骤,这样设置一个新数据库就会像一个月前一样导入初始结构,在第一次迁移,然后运行最近的 15 次迁移。这意味着您始终保留最近几次迁移的记录,以防您需要回滚它们或其他什么。
-
Rails 世界中的另一种常见做法,更直接地适用于上述问题,是将模型的副本保存在迁移文件中(即“及时冻结”的副本,可以这么说),并使用该副本在您的迁移中,而不是您的“真实”副本,它可能会被删除。当然,您不必包含整个模型的完整副本以及所有方法等 - 您可以删除特定迁移不需要的任何内容。
这会使您的迁移文件变大而且有点混乱,但谁在乎呢?不编辑迁移。它们只运行一次,而且大部分都被遗忘了。所以如果他们有点乱也没关系。
我没有在 sequelize 中尝试过第二种方法,但它对我来说在 Rails 中效果很好。
希望其中一些有用!
【讨论】:
感谢您的周到回复。我搞砸了这些,但找到了一个我更喜欢的新解决方案。 未来用户的更多信息。我使用了模式 #2:在迁移级别创建模型的副本,以获取迁移执行时数据库应该是什么样子的快照。【参考方案2】:感谢乔希的指点。
我的最终解决方案是继续使用 Umzug 和我一直使用的相同设置,并使用 sequelize.query 将任何基于模型的查询替换为原始查询。
Sequelize 提供了一些很好的功能来自动格式化结果,比如你的模型(JS 对象),所以你唯一要做的就是编写查询。我的是非常简单的插入/更新/删除。
这允许我使用节点的标准迁移模式,同时仍然具有从应用程序开始到现在的可重现迁移历史,并且不依赖于更改模型,并且还使用 JS 进行迁移而不是在 SQL 函数语言中操作数据.
希望这有帮助。
【讨论】:
【参考方案3】:(写一个答案,因为它有很多字符的评论)
为未来用户提供更多信息。
我使用类似于 joshua.paling 答案中的 #2 的模式:在迁移级别创建模型的副本,以获取迁移执行时数据库应该是什么样子的快照。
我最终遇到了一些错误:
SequelizeAssociationError:您在两个中使用了别名子项 单独的协会。别名关联必须具有唯一的别名。
解决方案是清除模型以及每次迁移执行之间的关联。
function clearModels(sequelize)
Object.keys(sequelize.models).forEach(m =>
Object.keys(sequelize.models[m].associations).forEach(a =>
delete sequelize.models[m].associations[a];
);
delete sequelize.models[m];
);
function clearCache(sequelize)
sequelize.importCache = ;
function migrated(sequelize)
return (name, migration) =>
clearModels(sequelize);
clearCache(sequelize);
;
const umzug, sequelize = getUmzug();
umzug.on('migrated', migrated(sequelize));
umzug.on('reverted', migrated(sequelize));
【讨论】:
以上是关于使用随时间变化的模型为 Sequelize 制作可重现的迁移字符串?的主要内容,如果未能解决你的问题,请参考以下文章
如何以编程方式制作随时间变化的 UIButton/CollectionViewCell Swift 3
best_state 在 pytorch 训练期间随模型而变化
拟合的 coxph 模型随时间变化的地层和集群的公式中的项排序而变化