How to create rake task and pass arguments

In the Ruby domain, rake is a Make-like utility that uses Ruby syntax to define tasks. Often used for simple, repeatable administrative tasks or for bundling a group of scripts together, Rake allows you to make the execution of your scripts more concise and manageable.
Today, we'll dive into the process of creating rake tasks and effectively passing arguments to them. Here's your step-by-step guide.

Using namespaces

Tasks can be grouped together in a namespace. This is particularly useful for organizing related tasks in a Rails application. Here's how to declare and invoke a task using a namespace:

namespace :do_some_action do
  desc 'task description'
  task :run do |task, args|
    puts "run me!"
  end
end
# usage: rake do_some_action:run

You can also call the task directly from within another rake task:

task :invoke_my_task do
  Rake::Task[:my_task].invoke(123, 456)
end

Task prerequisites

Rake allows you to define prerequisites for tasks. Prerequisite tasks receive the arguments passed to the calling task. It can be used for example to put logging into a file or another status of logging. Let's see an example:

desc "move rails logger to stdout"
task verbose: [:environment] do
  Rails.logger = Logger.new(STDOUT)
end
desc "move rails logger logs to debug level"
task verbose_to_debug: [:environment, :verbose] do
  Rails.logger.level = Logger::DEBUG
end
desc "move rails logger logs to info level"
task verbose_to_info: [:environment, :verbose] do
  Rails.logger.level = Logger::INFO
end

namespace :do_some_action do
  desc 'task description'
  task run: :verbose_to_debug do |task, args| # add verbose_to_debug
    puts "put rails loggging into debug logger"
    User.last # print to log as debug
  end
end
# usage: rake do_some_action:run

Using the default way

We can also go with standard parameter passing, but this method is tricky because we need to go with hacky array was when passing multiple parameters into rake task.

namespace :do_some_action do
  desc 'task description'
  task :run, [:user_id, :project_id] do |task, args|
    # add default when needed
    args.with_defaults(:user_id => '111', :project_id => '222')
    options = args.to_hash

    if options.values.all?(&:nil?)
      puts "Usage: rake do_some_action:run user_id=123 project_id=246"
      next
    end

    raise ArgumentError, 'user_id is required' if options[:user_id].blank?
    raise ArgumentError, 'project_id is required' if options[:project_id].blank?

    puts "do some stuff with passed params #{options}"
  end
end
# usage: be rake do_some_action:run - for default values
# usage: rake do_some_action:run\[123,456\] - for specific values

To run this task with arguments, you would run rake do_some_action:run\[123,456\]. Note the use of parentheses [ ] to pass the arguments and the lack of spaces between them. When you add spacing, then you will see an error.

Using OptParse

OptParse gem is a command line option parser class. It is much more advanced, but also easier to use than GetoptLong, and is a more Ruby-orientated solution.

require 'optparse'

namespace :do_some_action do
  task :run, [:user_id, :project_id] do |t, args|
    options = {}
    opts = OptionParser.new
    opts.banner = "Usage: rake do_some_action:run [options]"
    opts.on("-u", "--user_id ARG", Integer) { |num1| options[:user_id] = num1 }
    opts.on("-p", "--project_id ARG", Integer) { |num2| options[:project_id] = num2 }
    args = opts.order!(ARGV) {}
    opts.parse!(args)

    if options.values.all?(&:nil?)
      puts "Usage: rake do_some_action:run user_id=123 project_id=246"
      next
    end

    raise ArgumentError, 'user_id is required' if options[:user_id].blank?
    raise ArgumentError, 'project_id is required' if options[:project_id].blank?

    puts "do some stuff with passed params #{options}"
  end
end

# usage: rake do_some_action:run -- --user_id=123 --project_id=456

With this method, we're passing variables into options hash variable.

Using ARGV

Another method is to use ARGV and handle these parameters

namespace :do_some_action do
  task :run do
    options = {
      user_id: ARGV[1]&.split('=')&.last,
      project_id: ARGV[2]&.split('=')&.last,
    }
    if options.values.all?(&:nil?)
      puts "Usage: rake do_some_action:run user_id=123 project_id=456"
      next
    end

    raise ArgumentError, 'user_id is required' if options[:user_id].blank?
    raise ArgumentError, 'project_id is required' if options[:project_id].blank?

    puts "do some stuff with passed params #{options}"
  end
end

# usage: rake do_some_action:run user_id=123 project_id=456

Using linux-style passing arguments

We have (I think the best) method for passing parameters via ENV.

namespace :do_some_action do
  task :run do
    options = {
      user_id: ENV['user_id'],
      project_id: ENV['project_id']
    }

    if options.values.all?(&:nil?)
      puts "Usage: rake do_some_action:run user_id=123 project_id=246"
      next
    end

    raise ArgumentError, 'user_id is required' if options[:user_id].blank?
    raise ArgumentError, 'project_id is required' if options[:project_id].blank?

    puts "do some stuff with passed params #{options}"
  end
end

# usage: rake do_some_action:run user_id=123 project_id=246

In the above rake task, the arguments user_id and project_id are passed as environment variables and it's easy to understand for everybody without using hacky doubled -- like rake do_some_action:run –- --user_id=123 or even worse passing arrays into rake task.

Wrapping up

Using arguments in your rake tasks can be difficult if you don't know any tricks, but if you know how to handle passing parameters - it can be great and very useful in your daily work.

Happy coding!