Ruby on Rails Rake Tutorial

Ruby on Rails Rake Tutorial (aka. How rake turned me into an alcoholic)

by gregg on Jun 11, 2007

As a Rails developer you're probably familiar with running "rake" to run your tests or maybe you've used "rake db:migrate" to run your migrations. But do you really understand what's going on under the hood of these Rake tasks? Did you realize that you can write your own tasks or create your own library of useful Rake files?

Here are a few examples of how I've used Rake tasks:

  • Pulling a list of members to send out an email.
  • Doing nightly data calculations and reporting.
  • Expiring and regenerating caches.
  • Making backups of my database and subversion repository.
  • Running any sort of data manipulation script.
  • Pouring drinks to get a good buzz on.

In this article we're going to discover why Rake was created, and how it can help our Rails applications. By the end you should be able to write your own tasks, and learn how to get piss drunk using rake in no less then three steps.

This article is also available in FrenchRussianChinesePolish, and Spanish.

Table of Contents

  1. Why make, a retrospective?
  2. How did we get Rake?
  3. How does Rake work?
  4. How do Rake dependancies work?
  5. How do I document my Rake tasks?
  6. Rake Namespaces
  7. How do I write useful Ruby Tasks?
  8. How do I write Rake tasks for my Rails application?
  9. Can I access my Rails Models inside a Task?
  10. Where can I find more examples?

Why make, a retrospective?

In order to understand why we have Rake, we first need to take a look at Rake's older grandpa Make.

Travel with me for a moment back to the olden days when every piece of code had to be compiled, before interpreted languages and iPhones roamed the Earth.

Back then when you downloaded large programs they came as a bunch of raw source code and a shell script. This shell script would contain every line of code needed by your computer to compile/link/build the application. You'd run "install_me.sh" (the shell script), each line of code would then run (typically compiling each source file), and out would pop an executable you could run.

This worked fine for most people, except if you were one of the unlucky few developing the program. Every time you made a small change to the source and wanted to test it, you would have to rerun the shell script which would then recompile everything. Obviously this could take a long time with large programs.


Stuart Feldman

In 1977 (the year I was born) Stuart Feldman at Bell Labs invented "Make", which solved the problem of these long compile times. Make is used to compile programs just the same, with two big advances:

  1. Make can recognize which files/source changed since the last time the application was compiled. Using this information it will only compile the modified source files the second time Make is run. This dramatically reduces the time involved in recompiling a large program.
  2. Make also contains dependency tracking so you can tell the compiler that "Source File A" requires "Source File B" to compile properly, and "Source File B" requires "Source File C" to compile properly. Thus, if Make wants to compile "Source File A" and "Source File B" is not yet compiled, the system compiles B first.

It should also be explained that "Make" is simply an executable program like "dir" or "ls". In order for make to understand how to compile a program, a "makefile" needs to be created which references all the source and dependencies. "makefiles" have their own cryptic syntax which you don't need to know about.

Over the years Make has evolved and other programming languages even started using it. In fact, many Ruby coders used it before rake came along.

"But Ruby doesn't have to get compiled, so why would Ruby programmers use it?" I hear you exclaim.

Yes, Ruby is an interpreted language and we don't compile our code, so why were Ruby coders using Make files?

Well, for two big reasons:

  1. Task creation - With every large application you almost always end up writing scripts that you can run from the command line. You might want to clear the cache, run a maintenance task, or migrate the database. Rather than creating 10 separate shell scripts (or one big complex one) you can create a single "Makefile" in which you can organize things by task. Tasks then can be run by typing something like "make stupid" (which runs the stupid task).
  2. Dependancy Task Tracking - When you start writing a library of maintenance tasks, you start to notice that some tasks might partially repeat themselves. For instance, the task "migrate" and the task "schema:dump" both require getting a connection to the database. I could create a task called "connect_to_database", and set both "migrate" and "schema:dump" to depend on "connect_to_database". Then the next time I run "migrate", "connect_to_database" is run before the "migrate" task is run.

How did we get Rake?

Well, a few years ago Jim Weirich was working on a Java project where he was using Make. While working with his Makefile he realized how convenient things would be if he could write small snippets of Ruby inside his makefile. So he created rake. We were lucky enough to meet Jim at Railsconf last month, and he is a really nice guy:



Jim built in the ability to create Tasks, do dependency task tracking, and even built in the same timestamp recognition (rebuild only modified source files since last compilation). Obviously this last feature is not something we often use in Ruby, since we don't compile.

I always wondered what "Jim Weirich" did, now you know too! Jim never intended to write this code, I guess he was just born into it.

So how does rake work already?

I initially wanted to title this section "How to get wasted with Rake", but well, that's not very intuitive.

Lets say I wanted to get drunk, what steps would be involved?

  1. Purchase alcohol
  2. Mix myself a drink
  3. Achieve drunkenness

If I wanted to use Rake to call each of these tasks, I might create a file called "Rakefile" which contains something like this:

1
2
3
4
5
6
7
8
9
10
11
task :purchaseAlcohol do
  puts "Purchased Vodka"
end

task :mixDrink do
  puts "Mixed Fuzzy Navel"
end

task :getSmashed do
  puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
end

Then I can run each of these tasks from the same directory my rake file is in, kinda like this:

1
2
3
4
5
6
$ rake purchaseAlcohol
 Purchased Vodka
$ rake mixDrink
 Mixed Fuzzy Navel
$ rake getSmashed
 Dood, everthing's blurry, can I halff noth'r drinnnk?

Pretty cool! However, from a dependancy standpoint, I could run these tasks in any order. Although sometimes I wish I could "getSmashed" before I "mixDrink" or "purchaseAlcohol", this is simply not humanly possible.

So how do I express rake dependencies?

1
2
3
4
5
6
7
8
9
10
11
task :purchaseAlcohol do
  puts "Purchased Vodka"
end

task :mixDrink => :purchaseAlcohol do
  puts "Mixed Fuzzy Navel"
end

task :getSmashed => :mixDrink do
  puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
end

So now I'm saying that "In order to mixDrink, I must first purchaseAlcohol", and "In order to getSmashed I must mixDrink". As you might hope, the dependancies stack, thus:

1
2
3
4
5
6
7
8
9
$ rake purchaseAlcohol
 Purchased Vodka
$ rake mixDrink        
 Purchased Vodka
 Mixed Fuzzy Navel
$ rake getSmashed
 Purchased Vodka
 Mixed Fuzzy Navel
 Dood, everthing's blurry, can I halff noth'r drinnnk?

As you can see, now when I go to "getSmashed", the dependent tasks "purchaseAlcohol" and "mixDrink" get called.

After a while you might be tempted to expand your addictions, and thus expand your Rakefile. You might also be tempted to get your friends addicted. Just like a real software project, when you add people to your team, you need to make sure you have good documentation. The question becomes:

How do I document my rake tasks?

Rake comes with a very easy way to document tasks called "desc", here's how you'd use it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
desc "This task will purchase your Vodka"
task :purchaseAlcohol do
  puts "Purchased Vodka"
end

desc "This task will mix a good cocktail"
task :mixDrink => :purchaseAlcohol do
  puts "Mixed Fuzzy Navel"
end

desc "This task will drink one too many"
task :getSmashed => :mixDrink do
  puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
end

As you can see, each of my tasks now has a "desc". This allows me and my friends to type "rake -T" or "rake --tasks"

1
2
3
4
$rake --tasks
 rake getSmashed        # This task will drink one too many
 rake mixDrink          # This task will mix a good cocktail
 rake purchaseAlcohol  # This task will purchase your Vodka

Pretty easy huh?

Rake Namespaces

Once you become an alcoholic and you're using lots of Rake tasks, you may need a better way to categorize them. This is where namespaces come in. If I were to use namespaces in the above example, it might look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace :alcoholic do
  desc "This task will purchase your Vodka"
  task :purchaseAlcohol do
    puts "Purchased Vodka"
  end

  desc "This task will mix a good cocktail"
  task :mixDrink => :purchaseAlcohol do
    puts "Mixed Fuzzy Navel"
  end

  desc "This task will drink one too many"
  task :getSmashed => :mixDrink do
    puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
  end
end

Namespaces allow you to group tasks according to category, and YES, you can have more then one namespace inside a Rakefile. Now if I do a "rake --tasks" here's what I would see:

1
2
3
        rake alcoholic:getSmashed        # This task will drink one too many
        rake alcoholic:mixDrink          # This task will mix a good cocktail
        rake alcoholic:purchaseAlcohol  # This task will purchase your Vodka

So now to run these tasks you would obviously run "rake alcoholic:getSmashed".

How do I write useful Ruby Tasks?

Well, you simply write Ruby! No kidding. Recently I needed to create a script that created a couple of directories, so I ended up creating a Rake task that looked something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
desc "Create blank directories if they don't already exist"
task(:create_directories) do
  
  # The folders I need to create
  shared_folders = ["icons","images","groups"]
  
  for folder in shared_folders
    
    # Check to see if it exists
    if File.exists?(folder)
      puts "#{folder} exists"
    else
      puts "#{folder} doesn't exist so we're creating"
      Dir.mkdir "#{folder}"
    end
    
  end
end

By default rake has access to everything you find in File Utils, but you can include anything extra you want just like you would in Ruby.

How do I write Rake tasks for my Rails application?

Rails applications come with a bunch of pre-existing rake tasks, which you can list by going to your application directory and typing "rake --tasks". If you haven't tried this yet, go do it, I'll wait....

To create new rake tasks for your Rails app, you need to go open the /lib/tasks directory (which you should already have). In this directory if you create your own Rakefile, and call it "something.rake", the tasks will automatically get picked up. These tasks will be added to the list of application rake tasks, and you can run them from the root application directory. Lets take the above example and bring it into our rails application.

utils.rake

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace :utils do
  desc "Create blank directories if they don't already exist"
  task(:create_directories) do
  
    # The folders I need to create
    shared_folders = ["icons","images","groups"]
          
    for folder in shared_folders
        
      # Check to see if it exists
      if File.exists?("#{RAILS_ROOT}/public/#{folder}")
        puts "#{RAILS_ROOT}/public/#{folder} exists"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值