Rake & Task Automation

Master Rake for task automation and build processes. Learn to create custom tasks, manage dependencies, namespaces, and streamline development workflows.

Beginner 1 min read

Rake & Task Automation in Ruby

Rake is Ruby's powerful task automation tool that helps you automate common development tasks, build processes, and maintenance operations. Learn to create custom tasks, manage dependencies, and streamline your workflow.

Introduction to Rake

Rake (Ruby Make) is a task runner and build tool written in Ruby. It allows you to define tasks and their dependencies using Ruby code, making it both powerful and flexible.

Basic Rake Concepts

# Rakefile - main configuration file
# Basic task definition
task :hello do
  puts "Hello, Rake!"
end

# Task with description
desc "Greet the user"
task :greet do
  puts "Welcome to Rake automation!"
end

# Running tasks:
# rake hello
# rake greet
# rake -T  (list all tasks with descriptions)

# Task with parameters
task :say_hello, [:name] do |task, args|
  name = args[:name] || "World"
  puts "Hello, #{name}!"
end

# Run with: rake say_hello[Alice]
# Or: rake say_hello name=Alice

# Task with multiple parameters
task :create_user, [:name, :email, :role] do |task, args|
  puts "Creating user: #{args[:name]} (#{args[:email]}) - Role: #{args[:role]}"
end

# Run with: rake create_user[John,john@example.com,admin]

Task Dependencies and Organization

Rake excels at managing task dependencies and organizing complex automation workflows.

Task Dependencies

# Tasks with dependencies
task :compile do
  puts "Compiling source files..."
end

task :test => :compile do
  puts "Running tests..."
end

task :package => [:compile, :test] do
  puts "Creating package..."
end

task :deploy => :package do
  puts "Deploying application..."
end

# Alternative dependency syntax
task :build do
  puts "Building application..."
end

task :deploy_prod do
  puts "Deploying to production..."
end

# Define dependencies separately
task :deploy_prod => [:build, :test]

# Conditional dependencies
task :deploy_staging do
  puts "Deploying to staging..."
end

task :deploy_staging => :test if ENV['RUN_TESTS'] == 'true'

# Invoke other tasks programmatically
task :full_deployment do
  Rake::Task[:compile].invoke
  Rake::Task[:test].invoke
  puts "Full deployment completed!"
end

File Tasks and Pattern Rules

Rake provides special task types for working with files and automating file-based operations.

File Tasks

# File task - only runs if file doesn't exist or dependencies are newer
file 'output.txt' => ['input1.txt', 'input2.txt'] do
  puts "Creating output.txt from input files"
  File.open('output.txt', 'w') do |f|
    f.puts File.read('input1.txt')
    f.puts File.read('input2.txt')
  end
end

# Directory task - creates directory if it doesn't exist
directory 'build'
directory 'build/assets'

# File task depending on directory
file 'build/app.js' => ['build', 'src/app.js'] do
  puts "Compiling JavaScript..."
  FileUtils.cp 'src/app.js', 'build/app.js'
end

# Pattern rules for multiple files
rule '.o' => '.c' do |task|
  puts "Compiling #{task.source} to #{task.name}"
  sh "gcc -c #{task.source} -o #{task.name}"
end

# File lists and patterns
SOURCE_FILES = FileList['src/**/*.rb']
OBJECT_FILES = SOURCE_FILES.ext('.o')

desc "Compile all source files"
task :compile_all => OBJECT_FILES

# Clean tasks
desc "Remove build artifacts"
task :clean do
  rm_rf 'build'
  rm_f Dir.glob('*.o')
end

desc "Remove all generated files"
task :clobber => :clean do
  rm_f 'output.txt'
  rm_rf 'dist'
end

Namespaces and Task Organization

Organize complex projects using namespaces to group related tasks and avoid naming conflicts.

Namespace Definition

# Group related tasks in namespaces
namespace :db do
  desc "Create database"
  task :create do
    puts "Creating database..."
  end

  desc "Run database migrations"
  task :migrate do
    puts "Running migrations..."
  end

  desc "Seed database with initial data"
  task :seed => :migrate do
    puts "Seeding database..."
  end

  desc "Reset database (drop, create, migrate, seed)"
  task :reset => [:drop, :create, :migrate, :seed]

  task :drop do
    puts "Dropping database..."
  end
end

# Nested namespaces
namespace :assets do
  namespace :css do
    desc "Compile CSS files"
    task :compile do
      puts "Compiling CSS..."
    end

    desc "Minify CSS files"
    task :minify => :compile do
      puts "Minifying CSS..."
    end
  end

  namespace :js do
    desc "Compile JavaScript files"
    task :compile do
      puts "Compiling JavaScript..."
    end

    desc "Minify JavaScript files"
    task :minify => :compile do
      puts "Minifying JavaScript..."
    end
  end

  desc "Compile all assets"
  task :compile => ['css:compile', 'js:compile']
end

# Testing namespace
namespace :test do
  desc "Run unit tests"
  task :unit do
    puts "Running unit tests..."
    sh "ruby -Itest test/unit/*_test.rb"
  end

  desc "Run integration tests"
  task :integration do
    puts "Running integration tests..."
    sh "ruby -Itest test/integration/*_test.rb"
  end

  desc "Run all tests"
  task :all => [:unit, :integration]

  # Default task in namespace
  task :default => :all
end

# Usage:
# rake db:create
# rake db:migrate
# rake assets:css:compile
# rake test:unit
# rake test        # runs test:default which is test:all

Advanced Rake Features

Explore advanced Rake capabilities for complex automation scenarios and professional development workflows.

Dynamic Task Generation

# Generate tasks dynamically
ENVIRONMENTS = %w[development test staging production]

ENVIRONMENTS.each do |env|
  namespace env.to_sym do
    desc "Deploy to #{env}"
    task :deploy do
      puts "Deploying to #{env} environment..."
      # Environment-specific deployment logic
      case env
      when 'production'
        puts "Running production safety checks..."
      when 'staging'
        puts "Updating staging database..."
      end
    end

    desc "Run #{env} database migrations"
    task :migrate do
      puts "Running migrations in #{env}..."
      ENV['RAILS_ENV'] = env
      # Migration logic here
    end
  end
end

# Generate file processing tasks
%w[jpg png gif].each do |ext|
  desc "Process all .#{ext} files"
  task "process_#{ext}".to_sym do
    Dir.glob("images/*.#{ext}").each do |file|
      puts "Processing #{file}..."
      # Image processing logic
    end
  end
end

# Task templates
def define_test_task(name, pattern)
  desc "Run #{name} tests"
  task "test_#{name}".to_sym do
    puts "Running #{name} tests..."
    test_files = Dir.glob(pattern)
    test_files.each { |file| require file }
  end
end

define_test_task(:models, 'test/models/**/*_test.rb')
define_test_task(:controllers, 'test/controllers/**/*_test.rb')
define_test_task(:helpers, 'test/helpers/**/*_test.rb')

Task Configuration and Settings

Manage configuration, environment variables, and task settings for flexible automation.

Configuration Management

# Configuration management
class RakeConfig
  def self.load_config(env = 'development')
    config_file = "config/#{env}.yml"
    if File.exist?(config_file)
      require 'yaml'
      YAML.load_file(config_file)
    else
      {}
    end
  end

  def self.database_url(env = 'development')
    config = load_config(env)
    config.dig('database', 'url') || 'sqlite3:db/development.sqlite3'
  end
end

# Environment-aware tasks
task :setup_env do
  @env = ENV['RAILS_ENV'] || 'development'
  @config = RakeConfig.load_config(@env)
  puts "Setting up #{@env} environment..."
end

desc "Backup database"
task :backup => :setup_env do
  backup_file = "backups/db_#{@env}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.sql"
  puts "Backing up #{@env} database to #{backup_file}..."

  # Create backup directory
  mkdir_p 'backups'

  # Database-specific backup commands
  db_url = RakeConfig.database_url(@env)
  case db_url
  when /postgres/
    sh "pg_dump #{db_url} > #{backup_file}"
  when /mysql/
    sh "mysqldump #{db_url} > #{backup_file}"
  else
    puts "Backup not supported for this database type"
  end
end

# Multi-environment tasks
desc "Deploy to all environments"
task :deploy_all do
  %w[staging production].each do |env|
    puts "Deploying to #{env}..."
    ENV['RAILS_ENV'] = env
    Rake::Task[:deploy].invoke
    Rake::Task[:deploy].reenable  # Allow task to run again
  end
end

# Conditional tasks based on environment
if ENV['CI'] == 'true'
  desc "CI-specific setup"
  task :ci_setup do
    puts "Setting up CI environment..."
    # CI-specific configuration
  end

  # Make other tasks depend on CI setup
  task :test => :ci_setup
end

# Load external rake files
Dir.glob('lib/tasks/*.rake').each { |file| load file }

# Task with error handling
desc "Safe deployment with rollback"
task :safe_deploy do
  begin
    Rake::Task[:backup].invoke
    Rake::Task[:deploy].invoke
    puts "Deployment successful!"
  rescue => e
    puts "Deployment failed: #{e.message}"
    puts "Rolling back..."
    Rake::Task[:rollback].invoke
    raise
  end
end

Real-World Rake Applications

Practical examples of using Rake for common development and deployment tasks.

Complete Project Automation

# Complete project Rakefile example
require 'fileutils'
require 'yaml'

# Project configuration
PROJECT_NAME = 'MyApp'
VERSION_FILE = 'VERSION'
BUILD_DIR = 'build'
DIST_DIR = 'dist'

# Load version
def current_version
  File.exist?(VERSION_FILE) ? File.read(VERSION_FILE).strip : '0.1.0'
end

# Default task
desc "Run default development workflow"
task :default => [:clean, :test, :build]

# Setup and installation
desc "Setup development environment"
task :setup do
  puts "Setting up #{PROJECT_NAME} development environment..."

  # Create necessary directories
  %w[build dist tmp logs].each do |dir|
    mkdir_p dir
    puts "Created directory: #{dir}"
  end

  # Install dependencies
  if File.exist?('Gemfile')
    sh "bundle install"
  end

  # Setup database
  if File.exist?('config/database.yml')
    Rake::Task['db:setup'].invoke
  end

  puts "Setup completed!"
end

# Version management
namespace :version do
  desc "Show current version"
  task :show do
    puts "Current version: #{current_version}"
  end

  desc "Bump patch version"
  task :bump_patch do
    version_parts = current_version.split('.').map(&:to_i)
    version_parts[2] += 1
    new_version = version_parts.join('.')
    File.write(VERSION_FILE, new_version)
    puts "Version bumped to #{new_version}"
  end

  desc "Bump minor version"
  task :bump_minor do
    version_parts = current_version.split('.').map(&:to_i)
    version_parts[1] += 1
    version_parts[2] = 0
    new_version = version_parts.join('.')
    File.write(VERSION_FILE, new_version)
    puts "Version bumped to #{new_version}"
  end
end

# Build and packaging
desc "Build the application"
task :build => [:clean, :compile] do
  puts "Building #{PROJECT_NAME} v#{current_version}..."

  # Copy assets
  if Dir.exist?('assets')
    cp_r 'assets', "#{BUILD_DIR}/"
  end

  # Generate manifest
  File.open("#{BUILD_DIR}/manifest.json", 'w') do |f|
    f.puts JSON.pretty_generate({
      name: PROJECT_NAME,
      version: current_version,
      build_time: Time.now.iso8601
    })
  end

  puts "Build completed!"
end

desc "Create distribution package"
task :package => :build do
  puts "Creating distribution package..."

  mkdir_p DIST_DIR
  package_name = "#{PROJECT_NAME}-#{current_version}.tar.gz"

  sh "tar -czf #{DIST_DIR}/#{package_name} -C #{BUILD_DIR} ."
  puts "Package created: #{DIST_DIR}/#{package_name}"
end

# Development helpers
desc "Start development server"
task :server do
  puts "Starting development server..."
  sh "ruby -run -e httpd #{BUILD_DIR} -p 3000"
end

desc "Watch files and rebuild automatically"
task :watch do
  puts "Watching for file changes..."
  require 'listen'

  listener = Listen.to('src', 'assets') do |modified, added, removed|
    puts "Files changed: #{(modified + added + removed).join(', ')}"
    Rake::Task[:build].reenable
    Rake::Task[:build].invoke
  end

  listener.start
  sleep
end

# Maintenance tasks
desc "Clean up generated files"
task :clean do
  %w[build dist tmp/*.log].each do |pattern|
    rm_rf Dir.glob(pattern)
  end
  puts "Cleaned up generated files"
end

desc "Generate project documentation"
task :docs do
  puts "Generating documentation..."
  sh "yard doc"
  puts "Documentation generated in doc/"
end

# Release management
desc "Create a new release"
task :release => [:test, :build, :package] do
  version = current_version
  puts "Creating release #{version}..."

  # Tag release
  sh "git tag v#{version}"
  sh "git push origin v#{version}"

  puts "Release #{version} created!"
end

Best Practices

Rake Best Practices

  • Use descriptive task names and always add descriptions with desc
  • Organize tasks in namespaces to group related functionality
  • Handle dependencies properly using task prerequisites and invoke
  • Make tasks idempotent - safe to run multiple times
  • Use file tasks for build processes to avoid unnecessary work
  • Include error handling and meaningful error messages
  • Document complex tasks and provide usage examples
  • Test your rake tasks like any other code

💡 Pro Tips

  • Use rake -T to list all available tasks with descriptions
  • Use rake -P to show task dependencies
  • Load rake tasks from separate files using load or import
  • Use sh method for shell commands with automatic error handling
  • Create a :default task for common development workflows

🚀 Next Steps

Explore Guard for automated file watching and CLI tools development to complete your Ruby development toolkit.