Task dependencies – prerequisites
Sometimes, you have to write tasks that depend on other tasks. For example, when I'm going to seed data in my project, I want to clean all the persisting data that can break my code. In this case, we can say that our seed data task depends on the clean seed data task. The following code example shows us a Rakefile
for this case:
task :clean do puts 'Cleaning data...' end task :seed => :clean do puts 'Seeding data...' end
The preceding code executes the clean do
task before running the seed
task. The result of the execution of this task is shown below the following line of code:
$ rake seed Cleaning data... Seeding data...
It works as expected.
If you have to run the task from another namespace, pass its whole name as a string, as shown in the following code snippet:
namespace :db do task :clean do puts 'Cleaning data...' end end task :seed => 'db:clean' do puts 'Seeding data...' end
However, if the dependent task is in the same namespace, you don't have to pass it as a string, as shown in the following code snippet:
namespace :db do task :clean do puts 'Cleaning data...' end task :seed => :clean do puts 'Seeding data...' end end
Earlier in this chapter, we defined the default
rake task. To be honest, we did it just to understand what happens on running rake
without arguments and to introduce Rake in a few steps giving as less information as possible in an interactive way. However, in the practical word, nobody defines the default
rake task with an action. Setting dependencies is a convenient feature. It allows the default
task to refer to some other task as many times as you want without regression. For example, today, the default
task runs a doc:generate
task but tomorrow, we decide to run a test:run
task instead. In such a situation, we can just change the prerequisite and that's it. So, always define your default
rake task with the following template:
task :default => :some_task
It's also possible to pass many prerequisites for a task. The following line of code is an example of how to do this:
task :task1 => [:task2, :task3]
Multiple tasks definitions
A task might be specified more than once. Each specification adds its dependencies and implementation to the existing definition. This allows one part of a Rakefile
to specify the actions and a different Rakefile
(perhaps a separately generated one) to specify the dependencies.
For example, take a look a Rakefile
that contains the following code:
task :name => [:prereq1, :prereq2] do # action end
It can be rewritten as the following code:
task :name task :name => [:prereq1] task :name => [:prereq2] task :name do # action end
Passing arguments to the tasks
Assume that you have a rake task that sets the title for our blog and you want to pass it from the command line; this should be optional. If you don't pass the title of the blog, the default title should be set.
We have two solutions to solve this problem. The first solution is to pass parameters through the environment variable that is passed into the ENV
variable in Ruby code (ENV
is a hash-like accessor for environment variables, and it is available in any Ruby program). The second solution is using the built-in Rake syntax—you just pass variables to each task through square braces. The first use case doesn't allow you to pass variables for each task in isolation. The variables are shared among all the tasks in the Rakefile
. So, the preferable style is the second choice. However, we are shown two alternatives, which will be discussed in the next sections.
The first alternative
The first alternative is a case where we pass variables using environment variables. The following code represents a Rakefile
:
task :set_title do title = ENV['TITLE'] || 'Blog' puts "Setting the title: #{title}" end
The following code is a usage example:
$ rake set_title TITLE='My Blog' Setting the title: My Blog $ rake set_title # default title should be set in this case Setting the title: Blog
In the preceding example, the ENV
variable approach can be used without any caution. The following code snippet represents the collision in sharing the variable between the tasks. Check the following Rakefile
:
task :task1 do puts "#{ENV['TITLE']} in task1" end task :task2 do puts "#{ENV['TITLE']} in task2" end
The following code is an example of usage:
$ rake task1 task2 TITLE='test' test in task1 test in task2
You can see that the TITLE
variable is accessible in both the tasks and is the same. Sometimes, you don't want to get this behavior and you need to pass the variables to each task individually.
A variable declared within a rake command will not persist in the environment. The following terminal output will confirm this statement:
$ export TITLE='Default Title' $ rake set_title TITLE='My Blog' Setting the title: My Blog $ echo $TITLE Default Title
The second variant
The second variant has a built-in Rake feature. The following is the Rakefile
code:
task :set_title, [:title] do |t, args| args.with_defaults(:title => 'Blog') puts "Setting title: #{args.title}" end
Note
Please ignore the t
variable at this moment; you will see what it means and what its usages are in Chapter 2, Working with Files.
Look at args
, which is a hash-like object of the Rake::TasksArguments
class. It has a useful method that is used here, named with_defaults
, to merge the given arguments from the command line and the default values. If you don't pass the variables through the command line, the default variable for the title will be set.
The following code depicts how it may be used:
$ rake "set_title[My Blog]" Setting title: My Blog $ rake set_title Setting title: Blog
Here, to pass the argument as a string with space (My Blog
), I have enclosed the rake task with the argument within quotes. It's not the only case where I have to enclose the task name within double quotes. There are some terminals that don't understand the squared parentheses in the command line and should escape them with \
at the end of the code line of the rake task that is enclosed within the double quotes.
You are also able to pass multiple arguments to the rake task by separating them with a comma, as shown in the following line of command:
$ rake "name[Andrey,Koleshko]"
The task declaration for the preceding task is as follows:
task :name, [:first_name, :last_name] do |t, args| puts "First name is #{args.first_name}" puts "Last name is #{args.last_name}" end
Finally, you are able to pass variable-length parameters to the task with a comma, as we did in the previous example. In this case, you may use the extras
method on the given args
variable:
task :email, [:message] do |t, args| puts "Message: #{args.message}" puts "Recipients: #{args.extras}" puts "All variables: #{args.to_a}" end
In the following example, the first argument will be assigned to the message
variable on the args
variable and the remaining arguments will go to the extras
method. If you want to have an array that passes all the variables including the one associated with the message
variable, you can call the to_a
method on the args
variable, as demonstrated in the preceding Rakefile
.
$ rake "email[Hello Rake, ka8725@gmail.com, test@example.com]" Message: Hello Rake Recipients: ["ka8725@gmail.com", "test@example.com"] All variables: ["Hello Rake", "ka8725@gmail.com", "test@example.com"]