How to extend rake tasks with pre and post hooks in Ruby on Rails

Rake tasks are the workhorses of Ruby on Rails applications. From database migrations to asset compilation, they handle crucial operations that keep our applications running smoothly. But sometimes, the default behavior isn't quite enough. What if you need to back up your database before running migrations? Or maybe you want to send a notification after a deployment task completes?

Enter Rake's enhance method – a powerful but often overlooked feature that lets you extend existing Rake tasks without modifying their original code. In this article, I'll show you how to use enhance to add pre-script and post-script hooks to any Rake task. Let's dive in!

Understanding the Rake::Task#enhance method

At its core, the enhance method allows you to modify existing Rake tasks by adding prerequisites or appending code that runs after the task completes. Here's the basic syntax:

Rake::Task["task:name"].enhance(["prerequisite:task"]) do
  # Code to run after task:name completes
end

This simple pattern gives you two ways to extend a task:

  1. Adding prerequisite tasks that run before the main task
  2. Adding a block of code that runs after the main task

Let's break this down with some practical examples.

Adding pre-execution hooks

Pre-execution hooks are perfect when you need to ensure something happens before a task runs. For example, you might want to back up your database before running migrations. Here's how you could set that up:

# lib/tasks/database_hooks.rake
namespace :backup do
  desc "Backup the database before migrations"
  task :database => :environment do
    puts "🔒 Creating database backup..."
    # Your backup logic here
    system "pg_dump -Fc #{Rails.application.config.database_configuration[Rails.env]['database']} > #{Rails.root}/db/backups/#{Time.now.strftime('%Y%m%d%H%M%S')}_pre_migration.dump"
    puts "✅ Database backup completed!"
  end
end

# Now enhance the db:migrate task to run our backup first
Rake::Task["db:migrate"].enhance(["backup:database"])

When you run rake db:migrate, Rails will first execute backup:database before performing the actual migration. This ensures you always have a safety net before making database changes.

Adding post-execution hooks

Post-execution hooks run after a task completes, making them perfect for notifications, logging, or cleanup operations. Let's say you want to notify your team when a deployment task finishes:

# lib/tasks/deploy_hooks.rake
namespace :notify do
  desc "Send notification after deployment"
  task :team => :environment do
    puts "📣 Sending deployment notification..."
    # Your notification logic here
    DeploymentMailer.success_notification(Rails.env).deliver_now
    SlackNotifier.new.notify("Deployment to #{Rails.env} completed successfully!")
    puts "✅ Team notified of deployment!"
  end
end

# Enhance the deploy:finished task with a post-execution hook
Rake::Task["deploy:finished"].enhance do
  Rake::Task["notify:team"].invoke
end

In this example, after deploy:finished completes, our block will run and invoke the notify:team task.

Combining pre and post hooks

You can use both types of hooks together for more complex workflows:

# lib/tasks/test_hooks.rake
namespace :test do
  desc "Setup test environment"
  task :setup => :environment do
    puts "🛠 Setting up test environment..."
    # Setup logic here
  end

  desc "Clean up after tests"
  task :cleanup => :environment do
    puts "🧹 Cleaning up test artifacts..."
    # Cleanup logic here
  end
end

# Enhance the test task with both pre and post hooks
Rake::Task["test"].enhance(["test:setup"]) do
  Rake::Task["test:cleanup"].invoke
end

Now when you run rake test, it will:

  1. First run test:setup (pre-hook)
  2. Then run the original test task
  3. Finally run test:cleanup (post-hook)

Summary

Rake's enhance method is a powerful tool for extending existing tasks without modifying their original code. By using pre-execution hooks (prerequisites) and post-execution hooks (blocks), you can create more robust and flexible workflows in your Rails applications. Whether you're adding safety checks before critical operations or notifications after important tasks, this pattern helps you build more resilient systems.

Happy hooking!