I have a rake task that needs to insert a value into multiple databases.
I'd like to pass this value into the rake task from the command line, or from another rake task.
How can I do this?
You can specify formal arguments in rake by adding symbol arguments to the task call. For example:
require 'rake'
task :my_task, [:arg1, :arg2] do |t, args|
puts "Args were: #{args} of class #{args.class}"
puts "arg1 was: '#{args[:arg1]}' of class #{args[:arg1].class}"
puts "arg2 was: '#{args[:arg2]}' of class #{args[:arg2].class}"
end
task :invoke_my_task do
Rake.application.invoke_task("my_task[1, 2]")
end
# or if you prefer this syntax...
task :invoke_my_task_2 do
Rake::Task[:my_task].invoke(3, 4)
end
# a task with prerequisites passes its
# arguments to it prerequisites
task :with_prerequisite, [:arg1, :arg2] => :my_task #<- name of prerequisite task
# to specify default values,
# we take advantage of args being a Rake::TaskArguments object
task :with_defaults, :arg1, :arg2 do |t, args|
args.with_defaults(:arg1 => :default_1, :arg2 => :default_2)
puts "Args with defaults were: #{args}"
end
Then, from the command line:
> rake my_task[1,false] Args were: {:arg1=>"1", :arg2=>"false"} of class Rake::TaskArguments arg1 was: '1' of class String arg2 was: 'false' of class String > rake "my_task[1, 2]" Args were: {:arg1=>"1", :arg2=>"2"} > rake invoke_my_task Args were: {:arg1=>"1", :arg2=>"2"} > rake invoke_my_task_2 Args were: {:arg1=>3, :arg2=>4} > rake with_prerequisite[5,6] Args were: {:arg1=>"5", :arg2=>"6"} > rake with_defaults Args with defaults were: {:arg1=>:default_1, :arg2=>:default_2} > rake with_defaults['x','y'] Args with defaults were: {:arg1=>"x", :arg2=>"y"}
As demonstrated in the second example, if you want to use spaces, the quotes around the target name are necessary to keep the shell from splitting up the arguments at the space.
Looking at the code in rake.rb, it appears that rake does not parse task strings to extract arguments for prerequisites, so you can't do task :t1 => "dep[1,2]"
. The only way to specify different arguments for a prerequisite would be to invoke it explicitly within the dependent task action, as in :invoke_my_task
and :invoke_my_task_2
.
Note that some shells (like zsh) require you to escape the brackets: rake my_task\['arg1'\]
Options and dependencies need to be inside arrays:
namespace :thing do
desc "it does a thing"
task :work, [:option, :foo, :bar] do |task, args|
puts "work", args
end
task :another, [:option, :foo, :bar] do |task, args|
puts "another #{args}"
Rake::Task["thing:work"].invoke(args[:option], args[:foo], args[:bar])
# or splat the args
# Rake::Task["thing:work"].invoke(*args)
end
end
Then
rake thing:work[1,2,3]
=> work: {:option=>"1", :foo=>"2", :bar=>"3"}
rake thing:another[1,2,3]
=> another {:option=>"1", :foo=>"2", :bar=>"3"}
=> work: {:option=>"1", :foo=>"2", :bar=>"3"}
NOTE: variable task is the task object, not very helpful unless you know/care about Rake internals.
RAILS NOTE:
If running the task from Rails, it's best to preload the environment by adding => [:environment] which is a way to setup dependent tasks.
task :work, [:option, :foo, :bar] => [:environment] do |task, args|
puts "work", args
end
rake thing:work[1, 2, 3]
as it won't work and you'll get an error Don't know how to build task
rake thing:work'[1,2,3]'
rake thing:work\[1,2,3\]
, or this rake 'thing:work[1,2,3]'
:environment
symbol from your task. rails applications have an :environment task...
t
means task
, why not just use task
as the param name?
In addition to answer by kch (I didn't find how to leave a comment to that, sorry):
You don't have to specify variables as ENV
variables before the rake
command. You can just set them as usual command line parameters like that:
rake mytask var=foo
and access those from your rake file as ENV variables like such:
p ENV['var'] # => "foo"
p
mean?
If you want to pass named arguments (e.g. with standard OptionParser
) you could use something like this:
$ rake user:create -- --user test@example.com --pass 123
note the --
, that's necessary for bypassing standard Rake arguments. Should work with Rake 0.9.x, <= 10.3.x.
Newer Rake has changed its parsing of --
, and now you have to make sure it's not passed to the OptionParser#parse
method, for example with parser.parse!(ARGV[2..-1])
require 'rake'
require 'optparse'
# Rake task for creating an account
namespace :user do |args|
desc 'Creates user account with given credentials: rake user:create'
# environment is required to have access to Rails models
task :create do
options = {}
OptionParser.new(args) do |opts|
opts.banner = "Usage: rake user:create [options]"
opts.on("-u", "--user {username}","User's email address", String) do |user|
options[:user] = user
end
opts.on("-p", "--pass {password}","User's password", String) do |pass|
options[:pass] = pass
end
end.parse!
puts "creating user account..."
u = Hash.new
u[:email] = options[:user]
u[:password] = options[:pass]
# with some DB layer like ActiveRecord:
# user = User.new(u); user.save!
puts "user: " + u.to_s
puts "account created."
exit 0
end
end
exit
at the end will make sure that the extra arguments won't be interpreted as Rake task.
Also the shortcut for arguments should work:
rake user:create -- -u test@example.com -p 123
When rake scripts look like this, maybe it's time to look for another tool that would allow this just out of box.
--option-names
. My only suggestion would be to use exit
rather than abort
as abort
will leave you with a return code of 1 to the shell. If the rake task is a part of a higher-level script it's more common to assume a non-zero exit is some type of error.
--
? Like passing rake
arguments to the actual task or something? Like task :my_task, :*args do |t, args|
or something?
{username}
is here for. Where is it used? Why isn't it there in -u {username}
? Cheers
10.4.1
and reverted in 10.4.2
. github.com/ruby/rake/commit/…
I've found the answer from these two websites: Net Maniac and Aimred.
You need to have version > 0.8 of rake to use this technique
The normal rake task description is this:
desc 'Task Description'
task :task_name => [:depends_on_taskA, :depends_on_taskB] do
#interesting things
end
To pass arguments, do three things:
Add the argument names after the task name, separated by commas. Put the dependencies at the end using :needs => [...] Place |t, args| after the do. (t is the object for this task)
To access the arguments in the script, use args.arg_name
desc 'Takes arguments task'
task :task_name, :display_value, :display_times, :needs => [:depends_on_taskA, :depends_on_taskB] do |t, args|
args.display_times.to_i.times do
puts args.display_value
end
end
To call this task from the command line, pass it the arguments in []s
rake task_name['Hello',4]
will output
Hello
Hello
Hello
Hello
and if you want to call this task from another task, and pass it arguments, use invoke
task :caller do
puts 'In Caller'
Rake::Task[:task_name].invoke('hi',2)
end
then the command
rake caller
will output
In Caller
hi
hi
I haven't found a way to pass arguments as part of a dependency, as the following code breaks:
task :caller => :task_name['hi',2]' do
puts 'In Caller'
end
'task :t, arg, :needs => [deps]' is deprecated. Please use 'task :t, [args] => [deps]' instead.
I couldn't figure out how to pass args and also the :environment until I worked this out:
namespace :db do
desc 'Export product data'
task :export, [:file_token, :file_path] => :environment do |t, args|
args.with_defaults(:file_token => "products", :file_path => "./lib/data/")
#do stuff [...]
end
end
And then I call like this:
rake db:export['foo, /tmp/']
Actually @Nick Desjardins answered perfect. But just for education: you can use dirty approach: using ENV
argument
task :my_task do
myvar = ENV['myvar']
puts "myvar: #{myvar}"
end
rake my_task myvar=10
#=> myvar: 10
Another commonly used option is to pass environment variables. In your code you read them via ENV['VAR']
, and can pass them right before the rake
command, like
$ VAR=foo rake mytask
rake blah -- --these --go --to --a-program
(note the --
to tell rake that its switches have ended), see stackoverflow.com/questions/5086224/…
exit
at the end of the task. Exiting is the easier, but that will break any compound task that try to run the exiting task along others as exit
will halt task execution and exit Rake.
--switch1 value1 --switch2 value2
.
desc 'an updated version'
task :task_name, [:arg1, :arg2] => [:dependency1, :dependency2] do |t, args|
puts args[:arg1]
end
rake task_name[hello, world]
I just wanted to be able to run:
$ rake some:task arg1 arg2
Simple, right? (Nope!)
Rake interprets arg1
and arg2
as tasks, and tries to run them. So we just abort before it does.
namespace :some do
task task: :environment do
arg1, arg2 = ARGV
# your task...
exit
end
end
Take that, brackets!
Disclaimer: I wanted to be able to do this in a pretty small pet project. Not intended for "real world" usage since you lose the ability to chain rake tasks (i.e. rake task1 task2 task3
). IMO not worth it. Just use the ugly rake task[arg1,arg2]
.
_, arg1, arg2 = ARGV
as the first arg was seen to be the name of the rake task. But that exit
is a neat trick.
rake task[arg1,arg2] && rake task2 && rake task3
Not sure if that's less ugly than rake task[arg1,arg2] task2 task3
. Probably less efficient though.
_, *args = ARGV
is perfect for capturing all subsequent arguments! Thanks heaps!
I use a regular ruby argument in the rake file:
DB = ARGV[1]
then I stub out the rake tasks at the bottom of the file (since rake will look for a task based on that argument name).
task :database_name1
task :database_name2
command line:
rake mytask db_name
this feels cleaner to me than the var=foo ENV var and the task args[blah, blah2] solutions. the stub is a little jenky, but not too bad if you just have a few environments that are a one-time setup
dup
at the end: db = ARGV[1].dup
db = ARGV[1].dup unless ARGV[1].nil?
to prevent exception of duping a nil.
rake aborted! Don't know how to build task 'hello world'
The ways to pass argument are correct in above answer. However to run rake task with arguments, there is a small technicality involved in newer version of rails
It will work with rake "namespace:taskname['argument1']"
Note the Inverted quotes in running the task from command line.
To pass arguments to the default task, you can do something like this. For example, say "version" is your argument:
task :default, [:version] => [:build]
task :build, :version do |t,args|
version = args[:version]
puts version ? "version is #{version}" : "no version passed"
end
Then you can call it like so:
$ rake
no version passed
or
$ rake default[3.2.1]
version is 3.2.1
or
$ rake build[3.2.1]
version is 3.2.1
However, I have not found a way to avoid specifying the task name (default or build) while passing in arguments. Would love to hear if anyone knows of a way.
I like the "querystring" syntax for argument passing, especially when there are a lot of arguments to be passed.
Example:
rake "mytask[width=10&height=20]"
The "querystring" being:
width=10&height=20
Warning: note that the syntax is rake "mytask[foo=bar]"
and NOT rake mytask["foo=bar"]
When parsed inside the rake task using Rack::Utils.parse_nested_query
, we get a Hash
:
=> {"width"=>"10", "height"=>"20"}
(The cool thing is that you can pass hashes and arrays, more below)
This is how to achieve this:
require 'rack/utils'
task :mytask, :args_expr do |t,args|
args.with_defaults(:args_expr => "width=10&height=10")
options = Rack::Utils.parse_nested_query(args[:args_expr])
end
Here's a more extended example that I'm using with Rails in my delayed_job_active_record_threaded gem:
bundle exec rake "dj:start[ebooks[workers_number]=16&ebooks[worker_timeout]=60&albums[workers_number]=32&albums[worker_timeout]=120]"
Parsed the same way as above, with an environment dependency (in order load the Rails environment)
namespace :dj do
task :start, [ :args_expr ] => :environment do |t, args|
# defaults here...
options = Rack::Utils.parse_nested_query(args[:args_expr])
end
end
Gives the following in options
=> {"ebooks"=>{"workers_number"=>"16", "worker_timeout"=>"60"}, "albums"=>{"workers_number"=>"32", "worker_timeout"=>"120"}}
Most of the methods described above did not work for me, maybe they are deprecated in the newer versions. The up-to-date guide can be found here: http://guides.rubyonrails.org/command_line.html#custom-rake-tasks
a copy-and-paste ans from the guide is here:
task :task_name, [:arg_1] => [:pre_1, :pre_2] do |t, args|
# You can use args from here
end
Invoke it like this
bin/rake "task_name[value 1]" # entire argument string should be quoted
If you can't be bothered to remember what argument position is for what and you want do something like a ruby argument hash. You can use one argument to pass in a string and then regex that string into an options hash.
namespace :dummy_data do
desc "Tests options hash like arguments"
task :test, [:options] => :environment do |t, args|
arg_options = args[:options] || '' # nil catch incase no options are provided
two_d_array = arg_options.scan(/\W*(\w*): (\w*)\W*/)
puts two_d_array.to_s + ' # options are regexed into a 2d array'
string_key_hash = two_d_array.to_h
puts string_key_hash.to_s + ' # options are in a hash with keys as strings'
options = two_d_array.map {|p| [p[0].to_sym, p[1]]}.to_h
puts options.to_s + ' # options are in a hash with symbols'
default_options = {users: '50', friends: '25', colour: 'red', name: 'tom'}
options = default_options.merge(options)
puts options.to_s + ' # default option values are merged into options'
end
end
And on the command line you get.
$ rake dummy_data:test["users: 100 friends: 50 colour: red"]
[["users", "100"], ["friends", "50"], ["colour", "red"]] # options are regexed into a 2d array
{"users"=>"100", "friends"=>"50", "colour"=>"red"} # options are in a hash with keys as strings
{:users=>"100", :friends=>"50", :colour=>"red"} # options are in a hash with symbols
{:users=>"100", :friends=>"50", :colour=>"red", :name=>"tom"} # default option values are merged into options
To run rake tasks with traditional arguments style:
rake task arg1 arg2
And then use:
task :task do |_, args|
puts "This is argument 1: #{args.first}"
end
Add following patch of rake gem:
Rake::Application.class_eval do
alias origin_top_level top_level
def top_level
@top_level_tasks = [top_level_tasks.join(' ')]
origin_top_level
end
def parse_task_string(string) # :nodoc:
parts = string.split ' '
return parts.shift, parts
end
end
Rake::Task.class_eval do
def invoke(*args)
invoke_with_call_chain(args, Rake::InvocationChain::EMPTY)
end
end
namespace :namespace1 do
task :task1, [:arg1, :arg2, :arg3] => :environment do |_t, args|
p args[:arg1]
end
end
calling
rake namespace1:task1["1","2","3"]
No need to provide environment while calling
in zsh need to enclose calling in quotes
rake 'namespace1:task1["1","2","3"]'
rake
; i.e. rake 'namespace1:task1["1","2","3"]'
. Otherwise thank you for helpful answer.
One thing I don't see here is how to handle arbitrary arguments. If you pass arguments that are not listed in the task definition, they are still accessible under args.extras
:
task :thing, [:foo] do |task, args|
puts args[:foo] # named argument
puts args.extras # any additional arguments that were passed
end
While passing parameters, it is better option is an input file, can this be a excel a json or whatever you need and from there read the data structure and variables you need from that including the variable name as is the need. To read a file can have the following structure.
namespace :name_sapace_task do
desc "Description task...."
task :name_task => :environment do
data = ActiveSupport::JSON.decode(File.read(Rails.root+"public/file.json")) if defined?(data)
# and work whit yoour data, example is data["user_id"]
end
end
Example json
{
"name_task": "I'm a task",
"user_id": 389,
"users_assigned": [389,672,524],
"task_id": 3
}
Execution
rake :name_task
Success story sharing
WARNING: 'task :t, arg, :needs => [deps]' is deprecated. Please use 'task :t, [args] => [deps]' instead.
zsh: no matches found: ...
), so you need to escape the brackets:rake my_task\['arg1'\]
. From robots.thoughtbot.com/post/18129303042/…rake my_task[1,2]
instead ofrake my_task[1, 2]
. Otherwise you get the dreadedDon't know how to build task 'my_task[1,'
error and you'll be scratching your head for longer than you'd like to admit.