部署时ActiveRecord迁移报错:PG::UndefinedTable playlists表不存在
我之前也碰到过一模一样的问题,本地跑迁移完全正常,一用Capistrano部署就报playlists表不存在。先帮你理清楚问题根源,再给你几个靠谱的解决方案:
问题复盘
你写的迁移代码本身逻辑没问题,本地能正常执行就说明这点:
class AddTranslationsForPlaylistsYoutubesDjSetsAndPodcasts < ActiveRecord::Migration[6.0] def change reversible do |dir| dir.up do Djset.create_translation_table!({ title: :string }, { migrate_data: true, remove_source_columns: true }) Playlist.create_translation_table!({ title: :string }, { migrate_data: true, remove_source_columns: true }) YouTube.create_translation_table!({ title: :string }, { migrate_data: true, remove_source_columns: true }) Podcast.create_translation_table!({ title: :string }, { migrate_data: true, remove_source_columns: true }) end dir.down do Djset.drop_translation_table! migrate_data: true Playlist.drop_translation_table! migrate_data: true YouTube.drop_translation_table! migrate_data: true Podcast.drop_translation_table! migrate_data: true end end end end
报错的核心信息是:
PG::UndefinedTable: ERROR: relation "playlists" does not exist
LINE 8: WHERE a.attrelid = '"playlists"'::regclass
问题根源
本地和部署环境的核心差异在于:Capistrano执行迁移时会提前加载整个Rails应用环境,包括所有模型。而Globalize的create_translation_table!方法运行时,会尝试读取原模型表的列信息,此时如果Rails的Schema缓存里没有playlists表的记录(比如部署的是全新环境,或者缓存未及时更新),就会触发这个错误。
本地环境下,你的数据库已经存在playlists表,而且Rails默认不会提前预加载整个应用,所以不会出现这个问题。
解决方案
方案1:改用Globalize的底层迁移方法(最推荐)
不要直接调用模型类的方法,改用Globalize提供的不依赖模型的迁移助手,彻底避免加载模型带来的问题。注意这里要传入数据库的实际表名(比如Djset模型对应dj_sets表,YouTube对应you_tubes表):
class AddTranslationsForPlaylistsYoutubesDjSetsAndPodcasts < ActiveRecord::Migration[6.0] def change reversible do |dir| dir.up do create_translation_table :dj_sets, { title: :string }, migrate_data: true, remove_source_columns: true create_translation_table :playlists, { title: :string }, migrate_data: true, remove_source_columns: true create_translation_table :you_tubes, { title: :string }, migrate_data: true, remove_source_columns: true create_translation_table :podcasts, { title: :string }, migrate_data: true, remove_source_columns: true end dir.down do drop_translation_table :dj_sets, migrate_data: true drop_translation_table :playlists, migrate_data: true drop_translation_table :you_tubes, migrate_data: true drop_translation_table :podcasts, migrate_data: true end end end end
方案2:强制刷新Schema缓存
如果一定要保留调用模型方法的写法,可以在迁移开头强制刷新Rails的Schema缓存,确保能获取到最新的数据库表结构:
class AddTranslationsForPlaylistsYoutubesDjSetsAndPodcasts < ActiveRecord::Migration[6.0] def change # 清空并重新加载Schema缓存 ActiveRecord::Base.connection.schema_cache.clear! ActiveRecord::Base.connection.schema_cache.reload! reversible do |dir| dir.up do Djset.create_translation_table!({ title: :string }, { migrate_data: true, remove_source_columns: true }) Playlist.create_translation_table!({ title: :string }, { migrate_data: true, remove_source_columns: true }) YouTube.create_translation_table!({ title: :string }, { migrate_data: true, remove_source_columns: true }) Podcast.create_translation_table!({ title: :string }, { migrate_data: true, remove_source_columns: true }) end dir.down do Djset.drop_translation_table! migrate_data: true Playlist.drop_translation_table! migrate_data: true YouTube.drop_translation_table! migrate_data: true Podcast.drop_translation_table! migrate_data: true end end end end
方案3:检查Capistrano迁移配置
如果上面两个方法都不行,可以先排查部署环境的迁移状态:
- 在
config/deploy.rb中添加一个调试任务:
namespace :deploy do desc 'Check database migration status' task :check_migrations do on roles(:db) do within release_path do with rails_env: fetch(:rails_env) do execute :rake, 'db:migrate:status' end end end end end
- 执行
cap production deploy:check_migrations,确认创建playlists表的迁移已经被执行。如果没有,说明迁移顺序有问题,需要调整迁移文件的时间戳。
为什么本地没问题?
本地开发环境中,Rails默认不会在执行迁移前预加载整个应用,而且Schema缓存是实时更新的。而Capistrano为了部署效率,会提前加载应用环境,导致模型被提前加载,此时如果Schema缓存没有playlists表的信息,就会触发报错。
内容的提问来源于stack exchange,提问作者Alex Zakruzhetskyi

