如何在Rails中重新运行陈旧的迁移(附代码)

257 阅读2分钟

考虑以下情况。你正在审查你同事的一个拉动请求(PR),该PR有一个迁移,在数据库中添加了一个新的表。 你运行该迁移来测试PR。迁移创建了settings 表。你忘了扭转迁移,以免弄乱你的本地 DB 状态,于是你去了另一个分支继续工作。后来,PR 把这个表重命名为user_settings 。最后,它被合并了。

然后你更新主分支并运行迁移。令人惊讶的是,你在schema.rb 中有了变化。Git 显示它要添加settings 并删除user_settings 表。你意识到你运行的迁移现在有点过时了。你尝试用rails db:migrate:down VERSION=20220505072316 命令回滚这个迁移。但没有成功:

ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  table "user_settings" does not exist
: DROP TABLE "user_settings"

死路一条?请看如何解决这个问题。

解决方法

为了了解哪个迁移被运行,哪个没有被运行,Rails在DBschema_migrations 中有一个特殊的表。在DB控制台中查看它的结构(用rails db 命令或使用任何其他DB客户端跳入)。 这是PostgreSQL中这个表的描述:

~# \d schema_migrations
                 Table "public.schema_migrations"
 Column  │          Type          │ Collation │ Nullable │ Default
═════════╪════════════════════════╪═══════════╪══════════╪═════════
 version │ character varying(255) │           │ not null │
Indexes:
    "unique_schema_migrations" UNIQUE, btree (version)

每当你运行迁移时,它会在这个表中插入一条新的记录,其列version ,等于迁移文件的时间戳。例如,在文件20220505072316_create_settings.rb 中的迁移插入的记录是version=20220505072316

下次当你运行迁移时,Rails会检查迁移文件的时间戳是否已经在schema_migrations 表内。如果不在那里,它就应用迁移,否则就跳过它。这样一来,rails db:migrate 命令就保证了空闲性。

这个知识给了我们一个做什么的线索。如果从schema_migrations 中删除迁移的时间戳,我们可以再次运行它。这将增加user_settings 表,但不会删除过时的settings 表。但这不是什么大问题--我们可以在DB控制台中手动操作。 一旦我们这样做,rails db:migrate 命令就不会再产生任何差异。

在DB控制台中运行这些命令:

delete from schema_migrations where version = '20220505072316';
drop table settings;

运行rails db:migrate ,并确保你的schema.rb 上没有变化。