Rake gem explained
Rake is a task runner and it’s Ruby’s official build automation tool.
So what is a task? and how is it different than a function?
A task and a function are both a bunch of commands for the computer but they have one major difference. A task doesn’t return a value.
In fact, if we take a quick detour into the Functional Programming world we can explain this better.
In FP there is a concept called pure functions, and one criterion for a pure function is that it doesn’t have a side effect.
Ok, what is a side effect then?
Let’s look at an example. Say you have a silly method called add which takes two numbers and return the result. But because adding is such a big deal (!!) it logs the user activity and store the activity in the database.
As you can see in line 2 and 3 we’re doing something outside of the scope of the function that doesn’t have anything to do with delivering the results. That is called side effect, and thus our function is not pure.
Ok now that we know the definition of side effects and pure functions, I’d argue that:
Task is the polar opposite of Pure Function because it’s pure side effect.
Basic syntax
Let’s write a simple task in a file named `Rakefile` in the root of our project:
Now if you type in `rake clean` in your command line it would clean the build directory.
You can store your tasks in separate files with `.rake` extension and put them into your `rakelib` directory or if you’re using rails you can put them in your `lib/tasks` folder.
Namespacing
You can namespace your task like this:
And then you can simply say `rake asset:clean` and it will do the job for you.
Appending a task
Unlike `def` in Ruby, if you define a task in two different locations it will execute both if called (instead of overriding the first task).
Dependent tasks
If you have a task that needs bunch of other tasks to be run before it can start, you can just add them as dependencies:
In this example our uglify task need to wait for both concat and clean tasks to finish so that it can run.
If your task needs to access your rails app just add `environment` as a dependency.
Ok let’s compare Rake with some other build tools:
There are a number of awesome other build tools out there and I think it’s important to see how they approach the same problem differently.
Who knows maybe we decide to use something other than rake for our next project!
For that reason, I’ve picked a couple of tools and picked an objective to try them out:
Apache Ant: Is an XML-based build tool and very popular in java world. There is no code, it’s all XML!
Gulp: Is a popular build tool in JavaScript world and it has an awesome concept called pipes which we’ll see in a minute.
Objective
Say we have these three JS files in our `source` folder:
We’d like to concatenate and minify them and then run the minified version using node to make sure it works!
Just to throw in some complexity, we’d also like to name our final file after the owner of the machine that is running the build (in my case the end result should be `rudy.min.js`).
This is how it should look like:
You can access this project on GitHub if you want to play around with it.
Rake
This is how rake would do it:
As you can see the final file is being generated in two steps, first the source files are concatenated into `rudy.src.js` and then this file is minified into `rudy.min.js`. (We can do it in one step if we want by passing `out` in line 17 into `compile` in line 30)
Ant
As you can see `target` in Ant is equivalent to `task` in Rake.
I couldn’t find a way to do the concat and uglify in a single step, if you know how to do it please leave a comment. 🤓
Gulp
The pipe concept allows you to stick processes together to form a pipeline.
Cool, let’s see some more advanced usage of rake
Referencing a task in another part of your app
If you want to reference your task all you need to do is:
already_invoked feature
If you look into the source code there’s an instance variable called `@already_invoked` which prohibits a task from executing twice.
As you can see the task first is invoked twice but it is executed once.
Enhance your tasks
Say there is someone determined to drop you production database! 😱 You can do something like this to notify your Slack:
Disclaimer: if someone is really planning to drop your db they’ll find a better way! 😈😈
Multitask
Here is another nice feature; say you have a task that is dependent on a few other tasks that take a long time to complete. How about running the dependencies in parallel?
File tasks
If you have a task for creating a file, you can create a “file task” and give it the same name as your target file. This way, if the file already exists the task will not execute.
FileList
If you have a complex rule for targeting files you can use a FileList:
Let’s see what it does step by step:
- Grabs all the files with `.md` or `.markdown` extension.
- Excludes the one that starts with `~`.
- Excludes the one in the scratch folder.
- Excludes the ones that are not tracked by git.
Rules
Rule is basically a form of task that accepts a regex as task name. If your rake command matches that expression it will trigger the task.
It gets even more powerful if you pass a proc as a dependency. This way you can define your dependencies for rules dynamically.
Here is an example from rake documentation itself:
It creates a dynamic dependency by:
- Getting the task name (tn).
- Changing `.class` to `.java`.
- Changing the `classes` folder name to `src`.
Passing variables
If you want to pass variables in the command line, use the second argument in the task like this:
Here since we didn’t need the task instance itself, we replaced the first argument into the task by `_`(a naming convention).
I know it is not the prettiest thing, so if you’re using lots of arguments checkout Thor.
Ok, I’m done!
This post originally created for my talk in the Sydney Ruby Meetup, I’ve found Rake to be quite a powerful tool and I’m definitely going to use some these features in my projects.
If you know more cool tricks please let me know so that I can add it here, as I think Rake is a little underappreciated!