You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 03:20:23