class Nandi::SafeMigrationEnforcer

Constants

DEFAULT_AR_MIGRATION_DIR
DEFAULT_FILE_SPEC
DEFAULT_SAFE_MIGRATION_DIR

Public Class Methods

new(require_path: nil, safe_migration_dir: DEFAULT_SAFE_MIGRATION_DIR, ar_migration_dir: DEFAULT_AR_MIGRATION_DIR, files: DEFAULT_FILE_SPEC) click to toggle source
# File lib/nandi/safe_migration_enforcer.rb, line 19
def initialize(require_path: nil,
               safe_migration_dir: DEFAULT_SAFE_MIGRATION_DIR,
               ar_migration_dir: DEFAULT_AR_MIGRATION_DIR,
               files: DEFAULT_FILE_SPEC)
  @safe_migration_dir = safe_migration_dir
  @ar_migration_dir = ar_migration_dir
  @files = files

  require require_path unless require_path.nil?

  Nandi.configure do |c|
    c.migration_directory = @safe_migration_dir
    c.output_directory = @ar_migration_dir
  end
end

Public Instance Methods

run() click to toggle source
# File lib/nandi/safe_migration_enforcer.rb, line 35
def run
  safe_migrations = matching_migrations(@safe_migration_dir)
  ar_migrations = matching_migrations(@ar_migration_dir)

  return true if safe_migrations.none? && ar_migrations.none?

  enforce_no_ungenerated_migrations!(safe_migrations, ar_migrations)
  enforce_no_hand_written_migrations!(safe_migrations, ar_migrations)
  enforce_no_hand_edited_migrations!(ar_migrations)
  enforce_no_out_of_date_migrations!(safe_migrations)

  true
end

Private Instance Methods

enforce_no_hand_edited_migrations!(ar_migrations) click to toggle source
# File lib/nandi/safe_migration_enforcer.rb, line 114
    def enforce_no_hand_edited_migrations!(ar_migrations)
      hand_altered_migrations = ar_migrations.
        map { |m| [m, Nandi::Lockfile.get(m)] }.
        select do |filename, digests|
          Nandi::FileDiff.new(
            file_path: File.join(@ar_migration_dir, filename),
            known_digest: digests[:compiled_digest],
          ).changed?
        end

      if hand_altered_migrations.any?
        error = <<~ERROR
          The following migrations have had their generated content altered:

            - #{hand_altered_migrations.sort.join("\n  - ")}

          Please don't hand-edit generated migrations. If you want to write a regular
          ActiveRecord::Migration, please do so and add it to .nandiignore. Note that
          this will require additional review that will slow your PR down.
        ERROR

        raise MigrationLintingFailed, error
      end
    end
enforce_no_hand_written_migrations!(safe_migrations, ar_migrations) click to toggle source
# File lib/nandi/safe_migration_enforcer.rb, line 71
    def enforce_no_hand_written_migrations!(safe_migrations, ar_migrations)
      handwritten_migrations = ar_migrations - safe_migrations
      handwritten_migration_paths = names_to_paths(handwritten_migrations)

      if handwritten_migration_paths.any?
        error = <<~ERROR
          The following migrations have been written by hand, not generated:

            - #{handwritten_migration_paths.sort.join("\n  - ")}

          Please use Nandi to generate your migrations. In exeptional cases, hand-written
          ActiveRecord migrations can be added to the .nandiignore file. Doing so will
          require additional review that will slow your PR down.
        ERROR

        raise MigrationLintingFailed, error
      end
    end
enforce_no_out_of_date_migrations!(safe_migrations) click to toggle source
# File lib/nandi/safe_migration_enforcer.rb, line 90
    def enforce_no_out_of_date_migrations!(safe_migrations)
      out_of_date_migrations = safe_migrations.
        map { |m| [m, Nandi::Lockfile.get(m)] }.
        select do |filename, digests|
          Nandi::FileDiff.new(
            file_path: File.join(@safe_migration_dir, filename),
            known_digest: digests[:source_digest],
          ).changed?
        end

      if out_of_date_migrations.any?
        error = <<~ERROR
          The following migrations have changed but not been recompiled:

            - #{out_of_date_migrations.sort.join("\n  - ")}

          Please recompile your migrations to make sure that the changes you expect are
          applied.
        ERROR

        raise MigrationLintingFailed, error
      end
    end
enforce_no_ungenerated_migrations!(safe_migrations, ar_migrations) click to toggle source
# File lib/nandi/safe_migration_enforcer.rb, line 56
    def enforce_no_ungenerated_migrations!(safe_migrations, ar_migrations)
      ungenerated_migrations = safe_migrations - ar_migrations
      if ungenerated_migrations.any?
        error = <<~ERROR
          The following migrations are pending generation:

            - #{ungenerated_migrations.sort.join("\n  - ")}

          Please run `rails generate nandi:compile` to generate your migrations.
        ERROR

        raise MigrationLintingFailed, error
      end
    end
matching_migrations(dir) click to toggle source
# File lib/nandi/safe_migration_enforcer.rb, line 51
def matching_migrations(dir)
  names = Dir.glob(File.join(dir, "*.rb")).map { |path| File.basename(path) }
  FileMatcher.call(files: names, spec: @files)
end
names_to_paths(names) click to toggle source
# File lib/nandi/safe_migration_enforcer.rb, line 139
def names_to_paths(names)
  names.map { |name| File.join(@ar_migration_dir, name) }
end