http://jasonseifer.com/2010/04/06/rake-tutorial
Rake Tutorial
in Ruby
If you’re developing with Rails you’ve probably encountered rake once or twice. This blog post aims to walk you through where rake came from and an introduction on how to use it effectively in your Rails apps.
A Little Bit of History
Rake is the project of Jim Weirich. It’s a build tool. For a good laugh and an even more in
depth history check out the "rational.rdoc" from the Rake documentation.
Essentially, rake started as an idea for using Ruby inside of a Makefile. Though Jim doesn’t sound convinced from the tone in that document, it is a good idea.
What’s the need for an automated build system at all? As usual, Wikipedia has
the answer:
Historically, developers used build automation to call compilers and linkers from inside a build script versus attempting to make the compiler calls from the command line. It is simple to use the command line to pass a single source module to a compiler and
then to a linker to create the final deployable object. However, when attempting to compile and link many source code modules, in a particular order, using the command line process is not a reasonable solution. [sic]
As the build process grew more complex, developers began adding pre and post actions around the calls to the compilers such as a check-out from version control to the copying of deployable objects to a test location. The term “build automation” now includes
managing the pre and post compile and link activities as well as the compile and link activities.
It’s about Dependencies
This may be a bit of a stretch to say but build tools are about dependencies. One file or set of files depends on another set to get compiled, linked, or other fun things before the next set can be processed. The same idea exists in rake with tasks and task
dependencies. Let’s look at a simple rake task. Save the following as “Rakefile” in any directory:
What we’re saying here is that the file named “hello.tmp” depends on the directory "tmp". When rake runs across this, it’s going to create the directory "tmp" first before running the "hello.tmp" task. When you run it, you’ll see something like the following:
If you were to look at the "hello.tmp" file you would see the phrase "Hello". What happens if you run it again? You’ll see the same output again. What’s going on? Rake is generating the file again. It’s doing this because it can’t actually find the file tmp/hello.tmp
from that definition. Let’s redefine the task:
Now if you were to run it twice you would see something like this:
Rake now knows that the file task has been run.
Running Other Tasks
Rake tasks can take the form of having prerequisites and can depend on another task. Let’s say I wanted to get ready in the morning. My process would be something like this:
- Turn off alarm clock.
- Groom myself.
- Make coffee.
- Walk dog.
Let’s further assume that I have OCD and have to do all of these in order. In rake I might express my morning as follows:
If I were to run this as is I would type rake ready_for_the_day
and I’d see the following:
By running the ready_for_the_day
task it notices that the turn_off_alarm,
tasks are all prerequisites of the
groom_myself, make_coffee, and walk_dogready_for_the_day
task. Then it runs them all in the
appropriate order. You’ll notice that we can pass something in to the make_coffee
task. If we were having a really tough day we could
pass in a value to the COFFEE_CUPS environment variable and be more prepared:
Namespaces
Rake supports the concept of namespaces which essentially lets you group together similar tasks inside of one namespace. You’d then specify the namespace when you call a task inside it. It keeps things tidy while still being quite effective. In Rails, you might
notice thedb:migrate
task. In that example, db
is
the namespace and migrate is the task. Using the above example, we might put everything in to the morning
namespace:
Now if you were to run rake COFFEE_CUPS=3 morning:ready_for_the_day
you would have the same output as above, only it only took 3 cups
of coffee today. Score!
The Default Task
Rake has the concept of a default task. This is essentially the task that will be run if you type rake without any arguments. If we wanted our default task to be turning off the
alarm from the example above, we’d do this:
Running rake
now produces the following:
Describing Your Tasks
You can use the desc
method to describe your tasks. This is done on the line right above the task definition. It’s also what gives you
that nice output when you run rake -T to get a list of tasks. Tasks are displayed in alphabetical order. We’ll define some descriptions in our Rakefile (abbreviated for brevity):
Now when we run rake -T for our list of tasks we get the following output:
You can add in a string to get tasks matching that displayed. Running rake -T af
would show just the afternoon task.
Redefining Tasks
Let’s say you want to add on to an existing task. Perhaps you have another item in your grooming routine like styling your hair. You could write another task and slip it in as a dependency for groom_myself
but
you could also redefine groom_myself
later on (shortened for brevity but you get the idea):
Invoking Tasks
You may at some point want to invoke a task from inside another task. Let’s say, for example, you wanted to make coffee in the afternoon, too. If you need an extra upper after lunch you could do that the following way:
Which outputs:
A real world example of this is the rcov:all
task. I use this in Genius
Pool for aggregate rcov data. It’s shamelessly stolen from Clayton
Lengel-Zigich. Go check out that post for a good example of invoking other tasks from rake.
Refactoring
You’ll notice in the example above we’re delegating most of the work to already defined methods and tasks in the RSpec and Cucumber classes. As a general rule, try to keep your methods already defined other places and call them from rake with your specific
options and use cases. Let’s say I had a Rails application that e-mailed all accounts in the system that their account was expiring in a certain number of days. Here’s one way to write it:
A better way, that would let you test it more thoroughly would be to do the following: