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 -Tto list all available tasks with descriptions - Use
rake -Pto show task dependencies - Load rake tasks from separate files using
loadorimport - Use
shmethod for shell commands with automatic error handling - Create a
:defaulttask for common development workflows
🚀 Next Steps
Explore Guard for automated file watching and CLI tools development to complete your Ruby development toolkit.