Book Image

Rake Task Management Essentials

By : Andrey Koleshko
Book Image

Rake Task Management Essentials

By: Andrey Koleshko

Overview of this book

Table of Contents (18 chapters)
Rake Task Management Essentials
Credits
About the Author
Acknowledgements
About the Reviewers
www.PacktPub.com
Preface
Index

The structure of a Rake project


Apart from the necessary Rakefile, there is a technique that allows us to form a good structure of a Rake project. Say that you have a very complicated Rake project with a lot of tasks. It's a good idea to split them into separate files and include them in the Rakefile. Fortunately, Rake already has this feature and you shouldn't care about implementing this feature from the scratch. Just place your separated files to the rakelib folder (it can be changed to custom by passing the -R option), give these files a .rake extension, and that's it. You don't have to do anything additional. Files with the *.rake extensions are included in the Rakefile automatically for you. Nonstandard extension such as .rake for the files should not scare you. These are the usual Ruby files. There you can write any Ruby code, define their rake tasks, include the related libraries, and so on. So, take this feature as a good thing to refactor a Rake project.

To approve the things said in this section, please open the terminal and check the following example:

$ mkdir rakelib
$ cat > rakelib/clean.rake
task :clean do
  puts 'Cleaning...'
end
^D
$ cat > Rakefile
task :default => :clean
^D
$ rake
Cleaning...

In this example, ^D is a keyboard shortcut: Ctrl + D. The cat utility writes the standard output to the files here.

Using the import method to load other Rakefiles

It's possible to include other Ruby files or Rakefiles to describe a current Rakefile. It can be achieved by a standard require Ruby statement. However, what do we do when the including files depend on some method or variable defined in the describing Rakefile? To demonstrate the situation, create the following two files in a folder:

  • rakefile

  • dep.rb

The rakefile has some tasks definition, a method definition, and a require statement, as shown in the following code snippet:

require './dep.rb'

def method_from_rakefile
  puts 'it is a rakefile method'
end

task :a do
  puts 'task a'
end

task :b do
  puts 'task b'
end

The dep.rb file just defines a new task that has both the prerequisites tasks, a and b. Also, it calls the defined method, method_from_rakefile(), for some reason, as shown in the following code snippet:

method_from_rakefile()

task :c => [:a, :b] do
  puts 'task c'
end

Trying to run a rake task defined in Rakefile will cause an exception that says that there is no defined method_from_rakefile while the dep.rb file is loading:

$ rake c
rake aborted!
undefined method `method_from_rakefile' for main:Object
~/dep.rb:1:in `<top (required)>'
~/rakefile:1:in `<top (required)>'
(See full trace by running task with --trace)

The exception occurs when the dep.rb file is required by the Rakefile. The problem here is caused because the required file loaded even before the Rakefile could load. One of the possible solutions here is just to move the require statement to the last line of the Rakefile. As a result, the method and tasks required for the dep.rb file will be defined at the time of the dep.rb file being included in the Rakefile. To be honest, the solution seems like a hack; this is the Rake way.

Fortunately, Rake provides us with a tool to resolve this issue—the import method. It does what we really want here; the import statement may be used in any line of the Rakefile, and this doesn't apply to the loading process at all. The imported files will be loaded after the whole Rakefile is loaded. Its usage looks similar to the require statement and is shown in the following line of code:

import(filenames)

Here, you are able to pass more than one file.

There is one more feature of the import method. If you pass the filenames to the import task, they are evaluated first, and this allows us to generate the dependent files on the fly. Look at the following Rakefile:

task 'dep.rb' do
  sh %Q{echo "puts 'Hello, from the dep.rb'" > dep.rb}
end

task :hello => 'dep.rb'

import 'dep.rb'

This example generates the dep.rb file on the file due to the import 'dep.rb' call that evaluates the 'dep.rb' task. The result of the hello task execution is shown as follows:

$ rake hello
echo "puts 'Hello, from the dep.rb'" > dep.rb
Hello, from the dep.rb

It is a really helpful feature that can not only help you in writing the Rake project, but also in a simple Ruby project.

Running rake tasks from other tasks

Sometimes, you will have to execute some defined task from your task manually. For this purpose, you have two methods of the Rake::Task class: execute and invoke. The difference between the two methods is that execute doesn't call dependent tasks, but the invoke method does. Both of these methods also accept arguments that can be passed to the tasks if you need them. Their usage is the same and is shown as follows. The following is the first code:

Rake::Task['hello'].invoke

The following is the second code:

Rake::Task['hello'].execute

With the Rake::Task['hello'] code, we got the hello rake task. It returns an instance of the Rake::Task class and then, we are able to run any method on this. In the preceding examples, we called invoke and execute.

To get the namespaced task by name, like in the previous example, use a syntax or similar to the following line of code:

Rake::Task['my:hello']

One more difference between these methods is that the invoke method can't be executed twice without some trick. If you need to run the task more than once with the invoke method, use the reenable method as shown in the following code snippet:

Rake::Task['hello'].invoke
Rake::Task['hello'].reenable
Rake::Task['hello'].invoke

These capabilities can be used when you need to run some other rake task after a current task has been executed. Look at the following example that depicts how to use it in task actions. It demonstrates the usage of the invoke and reenable methods:

task :clean do
  puts 'cleaning data...'
end

task :process do
  puts 'processing some data...'
  Rake::Task['clean'].invoke
end

task :process_with_double_clean do
  puts 'processing some data...'
  Rake::Task['clean'].invoke
  Rake::Task['clean'].invoke
end

task :process_with_double_clean_and_reenable do
  puts 'processing some data...'

  Rake::Task['clean'].invoke
  Rake::Task['clean'].reenable
  Rake::Task['clean'].invoke
end

Try to paste this code in a Rakefile and run the process, process_with_double_clean, and process_with_double_clean_and_reenable tasks to find the difference between them. The following code is the output of the executions:

$  rake -f rakefile22 process
processing some data...
cleaning data...
$  rake -f rakefile22 process_with_double_clean
processing some data...
cleaning data...
$  rake -f rakefile22 process_with_double_clean_and_reenable
processing some data...
cleaning data...
cleaning data...