如何让ActiveRecord加载不同目录下序号相同的Addon迁移文件?
解决方案:为ActiveRecord Addon迁移自定义版本标识
要实现Addon迁移文件使用「序号+Addon前缀」作为版本标识,同时避免ActiveRecord的重复版本冲突,核心思路是自定义迁移版本的生成逻辑,让每个Addon的迁移版本唯一(格式如001_products_addon),而非依赖默认的时间戳或纯序号。
步骤1:自定义Addon迁移基类
创建一个继承自ActiveRecord::Migration的基类,重写version方法,从迁移文件路径中提取Addon名称和序号,拼接成唯一版本标识:
# lib/addon_migration.rb class AddonMigration < ActiveRecord::Migration[7.0] def self.version # 从调用栈获取迁移文件路径,提取Addon目录名和序号 file_path = caller.first.split(':').first addon_name = file_path.split('/')[-3] # 假设路径格式:addons/[addon_name]/db/migrate/xxx.rb sequence = file_path.split('_').first "#{sequence}_#{addon_name}" end end
所有Addon的迁移文件都继承这个基类,而非原生的ActiveRecord::Migration:
# addons/products_addon/db/migrate/001_create_products.rb class CreateProducts < AddonMigration def change create_table :products do |t| t.string :name t.timestamps end end end
步骤2:自定义迁移上下文(MigrationContext)
默认的MigrationContext会从文件名提取纯序号作为版本,导致重复冲突。我们需要自定义上下文,确保迁移实例的version字段使用我们拼接的唯一标识:
# lib/addon_migration_context.rb class AddonMigrationContext < ActiveRecord::MigrationContext def migrations super.map do |migration| # 重新设置迁移版本为「序号_Addon名」 addon_name = migration.filename.split('/')[-3] sequence = migration.filename.split('_').first migration.version = "#{sequence}_#{addon_name}" migration end end end
步骤3:编写Rake任务管理Addon迁移
创建Rake任务,批量或单独运行/回滚Addon的迁移:
# lib/tasks/addon_migrations.rake namespace :addons do desc "Run migrations for all addons" task migrate: :environment do Dir.glob(Rails.root.join('addons', '*')).each do |addon_dir| migrate_path = File.join(addon_dir, 'db', 'migrate') next unless Dir.exist?(migrate_path) context = AddonMigrationContext.new(migrate_path, ActiveRecord::SchemaMigration) context.migrate end end desc "Rollback last migration for a specific addon" task :rollback, [:addon_name] => :environment do |_, args| addon_dir = Rails.root.join('addons', args[:addon_name]) migrate_path = File.join(addon_dir, 'db', 'migrate') next unless Dir.exist?(migrate_path) context = AddonMigrationContext.new(migrate_path, ActiveRecord::SchemaMigration) context.rollback end desc "Generate a numbered migration for an addon" task :generate_migration, [:addon_name, :migration_name] => :environment do |_, args| addon_dir = Rails.root.join('addons', args[:addon_name]) migrate_dir = addon_dir.join('db', 'migrate') FileUtils.mkdir_p(migrate_dir) # 自动生成递增序号 existing_seqs = Dir.glob(migrate_dir.join('*.rb')).map { |f| f.split('_').first.to_i } next_seq = sprintf("%03d", (existing_seqs.max || 0) + 1) filename = "#{next_seq}_#{args[:migration_name].gsub(' ', '_')}.rb" file_path = migrate_dir.join(filename) # 写入迁移文件内容 class_name = args[:migration_name].camelize content = <<~RUBY class #{class_name} < AddonMigration def change end end RUBY File.write(file_path, content) puts "Created migration: #{file_path}" end end
关键说明
- 版本唯一性:每个Addon的迁移版本是
序号_Addon名,比如001_products_addon和001_tools_addon是完全不同的版本值,不会触发DuplicateMigrationError。 - 数据库存储:
schema_migrations表的version字段默认是varchar(255),完全支持存储这种格式的版本标识。 - 独立迁移:每个Addon的迁移目录相互独立,通过自定义上下文可以单独运行或回滚,不会影响主应用或其他Addon的迁移状态。
内容的提问来源于stack exchange,提问作者idchlife
相关产品推荐
相关产品推荐

