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:
directory "tmp"
file "hello.tmp" => "tmp" do
sh "echo 'Hello' >> 'tmp/hello.tmp'"
end
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:
[jason@brick:~/src]$ rake hello.tmp
(in /Users/jason/src)
echo 'Hello' > 'tmp/hello.tmp'
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:
directory "tmp"
file "tmp/hello.tmp" => "tmp" do
sh "echo 'Hello' > 'tmp/hello.tmp'"
end
Now if you were to run it twice you would see something like this:
[jason@brick:~/src]$ rake "tmp/hello.tmp"
(in /Users/jason/src)
mkdir -p tmp
echo 'Hello' > 'tmp/hello.tmp'
[jason@brick:~/src]$ rake "tmp/hello.tmp"
(in /Users/jason/src)
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:
1. Turn off alarm clock.
2. Groom myself.
3. Make coffee.
4. 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:
task :turn_off_alarm do
puts "Turned off alarm. Would have liked 5 more minutes, though."
end
task :groom_myself do
puts "Brushed teeth."
puts "Showered."
puts "Shaved."
end
task :make_coffee do
cups = ENV["COFFEE_CUPS"] || 2
puts "Made #{cups} cups of coffee. Shakes are gone."
end
task :walk_dog do
puts "Dog walked."
end
task :ready_for_the_day => [:turn_off_alarm, :groom_myself, :make_coffee, :walk_dog] do
puts "Ready for the day!"
end
If I were to run this as is I would type rake ready_for_the_day and I’d see the following:
[jason@brick:~/src]$ rake ready_for_the_day
(in /Users/jason/src)
Turned off alarm. Would have liked 5 more minutes, though.
Brushed teeth.
Showered.
Shaved.
Made 5 cups of coffee. Shakes are gone.
Dog walked.
Ready for the day!
By running the ready_for_the_day task it notices that the turn_off_alarm, groom_myself, make_coffee, and walk_dog tasks are all prerequisites of the ready_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:
[jason@brick:~/src]$ rake COFFEE_CUPS=5 make_coffee
(in /Users/jason/src)
Made 5 cups of coffee. Shakes are gone.
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 the db: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:
namespace :morning do
task :turn_of_alarm
....
end
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:
task :default => 'morning:turn_off_alarm'
Running rake now produces the following:
[jason@brick:~/src]$ rake
(in /Users/jason/src)
Turned off alarm. Would have liked 5 more minutes, though.
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):
...
desc "Make coffee"
task :make_coffee do
cups = ENV["COFFEE_CUPS"] || 2
puts "Made #{cups} cups of coffee. Shakes are gone."
end
...
Now when we run rake -T for our list of tasks we get the following output:
[jason@brick:~/src]$ rake -T
(in /Users/jason/src)
rake afternoon:make_coffee # Make afternoon coffee
rake morning:groom_myself # Take care of normal hygeine tasks.
rake morning:make_coffee # Make coffee
rake morning:ready_for_the_day # Get ready for the day
rake morning:turn_off_alarm # Turn off alarm.
rake morning:walk_dog # Walk the dog
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):
namespace :morning do
....
task :groom_myself do
puts "Brushed teeth."
puts "Showered."
puts "Shaved."
end
....
end
...
namespace :morning do
task :groom_myself do
puts "Styled hair."
end
end
[jason@brick:~/src]$ rake morning:groom_myself
(in /Users/jason/src)
Brushed teeth.
Showered.
Shaved.
Styled hair.
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:
namespace :afternoon do
task :make_coffee do
Rake::Task['morning:make_coffee'].invoke
puts "Ready for the rest of the day!"
end
end
Which outputs:
[jason@brick:~/src]$ rake afternoon:make_coffee COFFEE_CUPS=1
(in /Users/jason/src)
Made 1 cups of coffee. Shakes are gone.
Ready for the rest of the day!
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:
namespace :accounts do
desc "Email expiring accounts to let them know"
task :email_expiring => :environment do
date = ENV['from'] ? Date.parse(ENV['from']) : Date.today
accounts = Account.find(:all, :conditions => ["expiration_date = ?", date]
accounts.each do |account|
Notifier.deliver_account_expiration(account)
end
end
end
A better way, that would let you test it more thoroughly would be to do the following:
namespace :accounts do
desc "Email expiring accounts to let them know"
task :email_expiring => :environment do
date = ENV['from'] ? Date.parse(ENV['from']) : Date.today
Account.notify_expiring(date)
end
end
This lets you unit test your notify_expiring method on the account class and make sure that it’s doing what it’s supposed to do. This is a small, made up example, but you get the idea. Here’s an example from Resque:
desc 'Restart redis'
task :restart do
RedisRunner.stop
RedisRunner.start
end
Notice the delegation to the RedisRunner class methods? This is a great rake task
Rails
You can get access to your models, and in fact, your whole environment by making tasks dependent on the environment task. This lets you do things like run rake RAILS_ENV=staging db:migrate. Rails will autmatically pick up tasks in lib/tasks. Any files named with the .rake extension will get picked up when you do rake -T.
Scheduling Rake Tasks
You can use cron to schedule rake tasks. Let’s say you wanted to run the account email expiration task every night at 12:15 on your production server, you might have something like this:
15 * * * * cd /data/my_app/current && /usr/bin/rake RAILS_ENV=production accounts:email_expiring
Misc
Rake.original_dir gives you the directory that the original rake task was run from.
Derivatives
* Thor is a more class based approach to solving some of the things rake does as far as actual tasks go.
* Capistrano is the de facto standard for deploying rails apps. Its syntax is inspired pretty heavily by Rake, but it is definitely not rake.
Source http://jasonseifer.com/2010/04/06/rake-tutorial
Friday, December 10, 2010
Paperclip issue " is not recognized by the 'identify' command."
I tried to upload an image an image (paperclip plugin and windows 7) and then i received the following errror
Avatar C:/Users/machine~1/AppData/Local/Temp/stream20101210-2576-p7vfu8-0.png is not recognized by the 'identify' command.
This was the error obtained as i had not installed imagemagick in my new machine. To fix this i download image magick from http://www.imagemagick.org/script/binary-releases.php
Installed it and then restarted my machine.
Avatar C:/Users/machine~1/AppData/Local/Temp/stream20101210-2576-p7vfu8-0.png is not recognized by the 'identify' command.
This was the error obtained as i had not installed imagemagick in my new machine. To fix this i download image magick from http://www.imagemagick.org/script/binary-releases.php
Installed it and then restarted my machine.
Subscribe to:
Posts (Atom)