CrackedRuby logo

CrackedRuby

Rake

Overview

Rake provides a domain-specific language for defining tasks and their dependencies in Ruby applications. Tasks execute in order based on dependency relationships, with Rake ensuring prerequisites run before dependent tasks. The system centers on the Rake::Task class and uses a global task registry managed by Rake::Application.

Rake tasks accept parameters, invoke other tasks programmatically, and define file dependencies for build automation. The task definition syntax uses blocks to contain executable code, with dependency declarations establishing execution order.

require 'rake'

# Define a simple task
task :hello do
  puts "Hello, Rake!"
end

# Task with dependencies
task :compile => :clean do
  puts "Compiling source files"
end

# Task with parameters
task :deploy, [:environment] do |t, args|
  puts "Deploying to #{args[:environment]}"
end

Ruby applications typically define tasks in a Rakefile located in the project root. Rake loads this file automatically when invoked from the command line, making tasks available for execution.

# Rakefile example
desc "Remove temporary files"
task :clean do
  FileUtils.rm_rf('tmp')
end

desc "Run test suite"
task :test => :clean do
  sh 'ruby -Ilib:test test/test_*.rb'
end

task :default => :test

Basic Usage

Task definitions use the task method with a name symbol or hash for dependencies. The block contains executable Ruby code that runs when the task executes. Task names become available as command-line arguments to the rake command.

task :setup do
  Dir.mkdir('build') unless Dir.exist?('build')
  puts "Created build directory"
end

task :build => :setup do
  puts "Building application"
end

# Execute: rake build

Dependencies declare as symbols or arrays, with Rake resolving and executing prerequisites first. Multiple dependencies separate with commas or use array syntax for complex dependency graphs.

task :assets => [:javascript, :stylesheets, :images] do
  puts "All assets processed"
end

task :javascript do
  puts "Processing JavaScript files"
end

task :stylesheets do
  puts "Processing CSS files"
end

task :images do
  puts "Optimizing images"
end

Parameterized tasks accept arguments through the task definition and access them via the args parameter. Arguments pass from the command line using bracket syntax.

task :backup, [:database, :destination] do |t, args|
  db = args[:database] || 'production'
  dest = args[:destination] || '/backups'
  puts "Backing up #{db} database to #{dest}"
end

# Execute: rake backup[production,/tmp/backups]

File tasks create dependencies on filesystem timestamps, rebuilding targets only when source files change. This pattern supports efficient incremental builds for compiled assets or processed files.

file 'app.js' => ['src/main.js', 'src/utils.js'] do
  sh 'cat src/main.js src/utils.js > app.js'
end

task :assets => 'app.js'

Advanced Usage

Task enhancement extends existing tasks with additional behavior while preserving original functionality. Multiple enhancements accumulate, with each block executing in definition order.

task :deploy do
  puts "Basic deployment steps"
end

# Enhance the task with additional behavior
task :deploy do
  puts "Additional deployment configuration"
end

# Both blocks execute when :deploy runs

Namespace organization groups related tasks under qualified names, preventing naming conflicts and providing logical structure for complex applications. Nested namespaces create hierarchical task organization.

namespace :db do
  desc "Create database"
  task :create do
    puts "Creating database"
  end
  
  namespace :migrate do
    task :up do
      puts "Running migrations"
    end
    
    task :down do
      puts "Rolling back migrations"
    end
  end
end

# Execute: rake db:create
# Execute: rake db:migrate:up

Task invocation within other tasks uses the invoke method, executing the target task and its dependencies once per Rake session. Tasks invoked multiple times run only on the first invocation unless explicitly re-enabled.

task :prepare do
  Rake::Task[:clean].invoke
  Rake::Task[:setup].invoke
  puts "Preparation complete"
end

task :deploy => :prepare do
  puts "Deploying application"
  Rake::Task[:notify].invoke
end

Dynamic task generation creates tasks programmatically based on configuration or discovered files. This pattern scales task definitions without manual enumeration.

# Generate tasks for each environment
%w[development staging production].each do |env|
  namespace env.to_sym do
    task :deploy do
      puts "Deploying to #{env}"
      deploy_to_environment(env)
    end
    
    task :rollback do
      puts "Rolling back #{env}"
      rollback_environment(env)
    end
  end
end

Rule-based task definitions create pattern-matching tasks for file transformations. Rules define how to create target files from source files based on filename patterns.

rule '.css' => '.scss' do |t|
  sh "sass #{t.source} #{t.name}"
end

rule '.min.js' => '.js' do |t|
  sh "uglifyjs #{t.source} -o #{t.name}"
end

# Automatically handles: rake styles/main.css
# If styles/main.scss exists

Multitasking executes independent tasks concurrently using threads. This optimization reduces execution time for I/O bound operations or parallel-safe processes.

multitask :parallel_build => [:javascript, :css, :images] do
  puts "All assets built in parallel"
end

task :javascript do
  sleep 2  # Simulate compilation time
  puts "JavaScript compiled"
end

task :css do
  sleep 1  # Simulate processing time
  puts "CSS processed"
end

Error Handling & Debugging

Task failure propagation stops execution when tasks raise exceptions, preventing dependent tasks from running with invalid prerequisites. Rake preserves the original exception context for debugging.

task :validate_config do
  config_file = 'config/deploy.yml'
  unless File.exist?(config_file)
    raise "Configuration file #{config_file} not found"
  end
  
  config = YAML.load_file(config_file)
  required_keys = %w[server username deploy_path]
  
  missing = required_keys - config.keys
  unless missing.empty?
    raise "Missing configuration keys: #{missing.join(', ')}"
  end
end

task :deploy => :validate_config do
  puts "Configuration valid, proceeding with deployment"
end

Exception handling within tasks catches and processes errors while maintaining task execution flow. Recovery strategies include retrying operations, logging failures, or setting default values.

task :download_assets do
  urls = [
    'https://cdn.example.com/lib.js',
    'https://cdn.example.com/styles.css'
  ]
  
  urls.each do |url|
    begin
      puts "Downloading #{url}"
      # Simulate download operation
      raise Net::HTTPError, "Connection timeout" if rand > 0.7
      
      puts "Successfully downloaded #{File.basename(url)}"
    rescue Net::HTTPError => e
      puts "Failed to download #{url}: #{e.message}"
      puts "Using local fallback version"
    end
  end
end

Task debugging utilizes the --trace flag to display full execution paths and timing information. Custom debugging output provides visibility into task logic and variable states.

task :debug_build do
  puts "Current working directory: #{Dir.pwd}"
  puts "Environment: #{ENV['RAILS_ENV'] || 'development'}"
  
  files = Dir.glob('src/**/*.js')
  puts "Source files found: #{files.length}"
  files.each { |file| puts "  #{file}" }
  
  if files.empty?
    puts "WARNING: No source files found in src/ directory"
    puts "Build may not produce expected results"
  end
end

Conditional task execution prevents errors by checking prerequisites before attempting operations. Guards validate environment conditions, file existence, or system requirements.

task :deploy_production do
  unless ENV['RAILS_ENV'] == 'production'
    raise "This task must run in production environment"
  end
  
  unless system('which docker >/dev/null 2>&1')
    raise "Docker is required but not installed"
  end
  
  config_valid = system('rake config:validate >/dev/null 2>&1')
  unless config_valid
    raise "Configuration validation failed"
  end
  
  puts "All checks passed, deploying to production"
end

Production Patterns

Environment-specific task configuration adapts behavior based on deployment targets. Tasks query environment variables or configuration files to modify execution paths and parameters.

namespace :deploy do
  task :setup do
    @config = {
      'development' => {
        server: 'localhost',
        path: '/tmp/app',
        restart_cmd: 'touch tmp/restart.txt'
      },
      'production' => {
        server: 'prod.example.com',
        path: '/var/www/app',
        restart_cmd: 'sudo service app restart'
      }
    }
    
    @env = ENV['RAILS_ENV'] || 'development'
    @deploy_config = @config[@env]
    
    unless @deploy_config
      raise "Unknown environment: #{@env}"
    end
  end
  
  task :code => :setup do
    puts "Deploying code to #{@deploy_config[:server]}:#{@deploy_config[:path]}"
    sh "rsync -av ./ #{@deploy_config[:server]}:#{@deploy_config[:path]}"
  end
  
  task :restart => :code do
    puts "Restarting application"
    sh @deploy_config[:restart_cmd]
  end
end

Database migration tasks coordinate schema changes across environments with rollback capabilities. Tasks track migration versions and provide recovery mechanisms for failed updates.

namespace :db do
  task :migrate do
    require 'active_record'
    
    ActiveRecord::Base.establish_connection(
      adapter: 'sqlite3',
      database: "db/#{ENV['RAILS_ENV'] || 'development'}.sqlite3"
    )
    
    version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
    
    begin
      if version
        ActiveRecord::Migrator.migrate('db/migrate', version)
        puts "Migrated to version #{version}"
      else
        ActiveRecord::Migrator.migrate('db/migrate')
        current_version = ActiveRecord::Migrator.current_version
        puts "Migrated to version #{current_version}"
      end
    rescue ActiveRecord::IrreversibleMigration => e
      puts "Migration failed: #{e.message}"
      puts "Use VERSION parameter to specify target version"
      exit 1
    end
  end
  
  task :rollback do
    ENV['VERSION'] = (ENV['VERSION'].to_i - 1).to_s
    Rake::Task['db:migrate'].invoke
  end
end

Asset pipeline integration processes and optimizes web assets during deployment. Tasks coordinate compilation, compression, and fingerprinting for efficient browser caching.

namespace :assets do
  task :precompile => :environment do
    require 'sass'
    require 'uglifier'
    
    # Process SCSS files
    Dir.glob('app/assets/stylesheets/**/*.scss').each do |scss_file|
      css_file = scss_file.gsub('.scss', '.css').gsub('app/assets/', 'public/assets/')
      FileUtils.mkdir_p(File.dirname(css_file))
      
      engine = Sass::Engine.for_file(scss_file, {})
      css_content = engine.render
      
      File.write(css_file, css_content)
      puts "Compiled #{scss_file} -> #{css_file}"
    end
    
    # Process JavaScript files
    uglifier = Uglifier.new
    Dir.glob('app/assets/javascripts/**/*.js').each do |js_file|
      next if js_file.end_with?('.min.js')
      
      min_file = js_file.gsub('.js', '.min.js').gsub('app/assets/', 'public/assets/')
      FileUtils.mkdir_p(File.dirname(min_file))
      
      original = File.read(js_file)
      minified = uglifier.compile(original)
      
      File.write(min_file, minified)
      puts "Minified #{js_file} -> #{min_file}"
    end
  end
  
  task :clean do
    FileUtils.rm_rf('public/assets')
    puts "Cleaned compiled assets"
  end
end

Health check tasks validate application state and dependencies before and after deployments. Automated verification reduces deployment risk and provides early failure detection.

namespace :health do
  task :check do
    checks = [
      { name: 'Database Connection', proc: -> { check_database } },
      { name: 'Redis Connection', proc: -> { check_redis } },
      { name: 'External API', proc: -> { check_external_api } },
      { name: 'Disk Space', proc: -> { check_disk_space } }
    ]
    
    failed_checks = []
    
    checks.each do |check|
      print "Checking #{check[:name]}... "
      begin
        check[:proc].call
        puts ""
      rescue => e
        puts "✗ (#{e.message})"
        failed_checks << check[:name]
      end
    end
    
    if failed_checks.any?
      puts "\nFailed checks: #{failed_checks.join(', ')}"
      exit 1
    else
      puts "\nAll health checks passed"
    end
  end
  
  def check_database
    ActiveRecord::Base.connection.execute('SELECT 1')
  end
  
  def check_redis
    Redis.current.ping
  end
  
  def check_external_api
    response = Net::HTTP.get_response(URI('https://api.example.com/health'))
    raise "API returned #{response.code}" unless response.code == '200'
  end
  
  def check_disk_space
    stat = File.statvfs('.')
    available_mb = (stat.f_bavail * stat.f_bsize) / (1024 * 1024)
    raise "Only #{available_mb}MB available" if available_mb < 100
  end
end

Reference

Core Classes and Methods

Class/Method Parameters Returns Description
Rake::Task.define_task(name, &block) name (Symbol/Hash), block (Proc) Rake::Task Creates new task with optional dependencies
Rake::Task[] name (String/Symbol) Rake::Task Retrieves existing task by name
Rake::Task#invoke(*args) args (Array) nil Executes task with arguments once per session
Rake::Task#execute(args) args (Rake::TaskArguments) nil Runs task block without dependency checking
Rake::Task#reenable None self Allows task to be invoked again
Rake::Task#prerequisites None Array<String> Returns array of dependency task names
Rake::FileTask.new(name, app) name (String), app (Rake::Application) Rake::FileTask Creates file-based task with timestamp checking

Task Definition Methods

Method Parameters Returns Description
task(args, &block) args (Symbol/Hash), block (Proc) Rake::Task Defines basic task with dependencies
file(args, &block) args (String/Hash), block (Proc) Rake::FileTask Creates file task with source dependencies
directory(dir) dir (String) Rake::FileTask Defines directory creation task
rule(pattern => source, &block) Pattern mapping, block (Proc) nil Creates pattern-based file transformation rule
multitask(args, &block) args (Symbol/Hash), block (Proc) Rake::MultiTask Defines task with parallel prerequisite execution
namespace(name, &block) name (Symbol), block (Proc) nil Groups tasks under qualified namespace
desc(description) description (String) nil Sets description for next defined task

Command Line Options

Option Description
--dry-run, -n Display tasks without executing them
--tasks, -T Show available tasks with descriptions
--trace Enable full backtrace on errors
--verbose, -v Enable verbose output
--quiet, -q Suppress standard output
--rakefile FILE, -f Use specified rakefile
--directory DIR, -C Change to directory before running
--no-system Ignore system-wide rakefiles
--version Display Rake version

Environment Variables

Variable Description Default
RAKE_SYSTEM System-wide rakefile location /etc/rakefile
RAKEFILE Default rakefile name rakefile, Rakefile, rakefile.rb, Rakefile.rb
RAKE_COLUMNS Terminal width for task formatting Auto-detected

Task Arguments and Parameters

# Task with named parameters
task :deploy, [:environment, :branch] do |t, args|
  env = args[:environment] || 'staging'
  branch = args[:branch] || 'main'
  # Task implementation
end

# Access with defaults
task :backup, [:database] do |t, args|
  args.with_defaults(database: 'production')
  # args[:database] now guaranteed to have value
end

File Task Patterns

Pattern Type Example Usage
Single file dependency file 'output.txt' => 'input.txt' Basic file transformation
Multiple file sources file 'app.js' => Dir.glob('src/*.js') Concatenation or compilation
Extension rules rule '.o' => '.c' Generic file type transformation
Regex patterns rule /\.html$/ => '.md' Pattern-based transformations
Proc-based sources rule '.css' => proc { |target| target.sub('.css', '.scss') } Dynamic source file detection

Exception Classes

Exception Raised When Recovery Strategy
Rake::TaskNotFoundError Task name doesn't exist Check task name spelling, verify namespace
Rake::RuleRecursionError Rule creates circular dependency Redesign rule patterns, check file dependencies
Rake::TaskArgumentError Wrong number of task arguments Verify task parameter definition and invocation
Rake::CommandFailedError Shell command returns non-zero exit Add error handling, check command availability

Advanced Configuration

# Custom application instance
app = Rake::Application.new
Rake.application = app

# Task enhancement patterns
Rake::Task[:existing].enhance do
  puts "Additional behavior"
end

# Conditional task loading
if File.exist?('config/database.yml')
  load 'lib/tasks/db.rake'
end