lib/ree_lib/packages/ree_migrator/package/ree_migrator/functions/apply_migrations.rb



# frozen_string_literal: true


class ReeMigrator::ApplyMigrations
  include Ree::FnDSL

  fn :apply_migrations do
    link :is_blank, from: :ree_object
    link :index_by, from: :ree_array
    link :apply_migration
    link :logger, from: :ree_logger
  end

  RUBY_EXT = '*.rb'
  DATA = 'data'
  SCHEMA = 'schema'

  InvalidMigrationYmlErr = Class.new(StandardError)
  MigrationNotFoundErr = Class.new(StandardError)

  contract(
    Sequel::Database,
    String,
    String,
    String => ArrayOf[String]
  ).throws(InvalidMigrationYmlErr, MigrationNotFoundErr)
  def call(connection, migrations_yml_path, schema_migrations_path, data_migrations_path)
    logger.info("Parsing migrations.yml from #{migrations_yml_path}")

    migrations = YAML.load(File.read(migrations_yml_path))
    return [] if is_blank(migrations)

    applied_schema_migrations = indexed_migrations(connection, SCHEMA)
    applied_data_migrations = indexed_migrations(connection, DATA)
    schema_migrations = Dir.glob(File.join(schema_migrations_path, RUBY_EXT))
    data_migrations  = Dir.glob(File.join(data_migrations_path, RUBY_EXT))

    migrations = migrations.map do |migration|
      if !migration.is_a?(Hash) || !(migration.keys - [SCHEMA, DATA]).empty?
        raise InvalidMigrationYmlErr.new(
          "Invalid migrations.yml. Example of valid format:\n- schema: SCHEMA_MIGRATION_FILE_NAME.rb\n- data: DATA_MIGRATION_FILE_NAME.rb"
        )
      end

      migration_path = if migration.has_key?(SCHEMA)
        run_migration(
          connection,
          :schema,
          migration[SCHEMA],
          applied_schema_migrations,
          schema_migrations,
          schema_migrations_path
        )
      elsif migration.has_key?(DATA)
        run_migration(
          connection,
          :data,
          migration[DATA],
          applied_data_migrations,
          data_migrations,
          data_migrations_path
        )
      end

      migration_path
    end.compact

    migrations
  end

  private

  def indexed_migrations(connection, type)
    migrations = connection[:migrations]
      .select(:filename)
      .where(type: type)
      .all

    index_by(migrations) { _1[:filename] }
  end

  def run_migration(connection, migration_type, migration_name, applied_migrations, migrations, migrations_path)
    return nil if applied_migrations.include?(migration_name)

    migration_path = migrations.detect { _1.include?(migration_name) }

    raise MigrationNotFoundErr.new(
      "schema migration file for #{migration_name} not found in #{migrations_path}"
    ) if !migration_path

    apply_migration(connection, migration_path, migration_type)

    migration_path
  end
end