Archiving Rails Database Migrations by Year

2 min read Kane Jamison Kane Jamison

We are approaching 400 Rails database migrations in Content Harmony and they are getting a little unwieldy in my editor sidebar.

Simon Chiu has a nice rake task he posted that helps archive old database migration, and his post explains some of the reasons for doing so aside from being annoyed by scrolling.

His version pushes all files into a single archive, and I wanted a variation I could run that would organize these by year. I also didn't want to archive migrations from the current year, nor did I want this rake task to ever get run in production just in case, so here is a slight update on his rake task that takes those requirements into account:

namespace :db do
  namespace :migrate do
    desc "Archive migration files from past years into yearly subdirectories"
    task :archive do
      if Rails.env.production?
        puts "ERROR: This task should not be run in production environment."
        return
      end

      current_year = Date.current.year
      migrations_path = Rails.root.join("db/migrate")
      archives_path = Rails.root.join("db/migrate/archives")

      puts "Archiving migration files (excluding #{current_year})..."

      # Create archives directory if it doesn't exist
      FileUtils.mkdir_p(archives_path)

      # Get all migration files
      migration_files = Dir.glob(File.join(migrations_path, "*.rb"))

      archived_count = 0

      migration_files.each do |file|
        filename = File.basename(file)

        # Extract timestamp from filename (first 14 characters: YYYYMMDDHHMMSS)
        timestamp = filename[0, 14]

        # Skip if timestamp is invalid
        next unless timestamp.match?(/^\d{14}$/)

        # Extract year from timestamp
        file_year = timestamp[0, 4].to_i

        # Skip files from current year
        next if file_year >= current_year

        # Create year-specific archive directory
        year_archive_path = File.join(archives_path, file_year.to_s)
        FileUtils.mkdir_p(year_archive_path)

        # Move the file
        destination = File.join(year_archive_path, filename)
        FileUtils.mv(file, destination)

        puts "Archived #{filename} to archives/#{file_year}/"
        archived_count += 1
      end

      if archived_count > 0
        puts "Successfully archived #{archived_count} migration file(s)."
        puts "Current migration files remain in db/migrate/"
      else
        puts "No migration files to archive (all files are from #{current_year} or later)."
      end
    end
  end
end

Here is what you'll see in console upon running this:

And here is what you'll see if you run this twice in the same year:

If you're running Standard or Rubocop you'll likely need to make a change like this as well to ignore older migrations that don't pass linting:

ignore:
  # ignore old database migrations
  - "db/migrate/archives/**/*"

"Can't you just drag and drop these files manually?"

Yeah absolutely. But this way is more fun.