I’ve got a bunch of little tasks that we do at work that we run through cron, and most of them involve our rails application. We’ve run a lot of them through script/runner, which is a fine tool in its own right, but after doing a little research, I think turning our simple Ruby tasks into rake tasks makes more sense for a few reasons. First it might be easier to grok for someone coming up to speed on our code if we can just tell them that once a week we run a rake task that sends out a report email to our sales guys, instead of having to explain that we run a Ruby script through script/runner to load our Rails environment, and possibly have to explain what script/runner even is (a lot of candidates we talk to haven’t really explored the Rails skeleton that you get with any new project… makes me kind of sad). Second, if you forget what it should be, you can easily do a search with rake -T.
So, what I’m going to do here is show you how to write a simple rake task for deleting a plugin (script/plugin doesn’t have an uninstall option, which I find strange). You could probably just extend script/plugin for this (I know you could… Rails is all about extensibility), but it’s a simple example with an actual useful bit of functionality so it will let you see how things evolve. I’m a bit wordy, so this is going to be broken into two posts for convenience sake.
First, let’s start with an empty rails project. So, go to wherever you keep your one-off code, and type:
$ rails rake_test_example
$ cd rake_test_example
Alright, so, I’m assuming some familiarity with Rails, rake, and Ruby here, so if you don’t know what’s going on at this point, you probably should start somewhere else. If you’re still here, I’m assuming you’re ready for the straight-talk. So, when writing your own rake task, the first thing you should know is where they go. In your Rails application, there is a directory called “lib”, and inside of lib is another directory called “tasks”. This is where you put your custom rake tasks. Any files you put in here called “some_file_name”.rake will be picked up by the rake executable, and including in the list of available tasks. How does rake do this? Glad you asked. In your Rails install directory, you can find the salient code in “railties/lib/tasks/rails.rb”, and the line that actually does it is:
Dir["#{RAILS_ROOT}/vendor/plugins/*/**/tasks/**/*.rake"].sort.each /
{ |ext| load ext }
See, no magic, just some Ruby code. Anyway, glad you’re curious, but let’s move on. So, when you’re writing your own plugin, the first thing to remember is that you put it in “lib/tasks/” and the filename needs to have the “.rake” extension. What goes in a rake file you ask? Well, let’s continue. Create a new file “lib/tasks/example.rake” and open it in a text editor. Type in the following:
task :hello_rake do
puts "Hello, Rake"
end
Save the file and let’s run our rake task now. At the command line run
$ rake hello_rake
(in .../projects/code/rails/engines_demo)
Hello, Rake
And voila, you now have written your first rake task. If you now tell rake to list its tasks though, you won’t find your entry in the list. Searching for it won’t yield you any more luck either. (Give it a shot here)
$ rake --tasks
$ rake -T hello_rake
No results. All we need to do is add one line to our custom task and we’re good to go.
desc "This is our hello world task"
task :hello_rake do
puts "Hello, Rake"
end
I personally find this a little unintuitive (opposed to putting the desc block inside the task you’re documenting), but if you think of it as more of an annotation it sort of makes sense. Anyway, if we run our command to search for the task, we now have an entry for our custom task
$ rake -T hello_rake
(in .../projects/code/rails/engines_demo)
rake hello_rake # This is our hello world task
Another thing that you often see in rake tasks is namespacing. For instance, you see a lot of tasks like db:migrate, db:rollback, spec:models, etc. Namespacing is a good way of keeping things neat so that you can hierarchically organize similar tasks. Adding namespacing to our custom tasks is almost as easy as adding our documentation for the task. Open up example.rake and change the code like so:
namespace :custom do
desc "This is our hello world task"
task :hello_rake do
puts "Hello, Rake"
end
end
After we save that and run our command to search for the task, we now get this:
$ rake -T hello_rake
(in .../projects/code/rails/engines_demo)
rake custom:hello_rake # This is our hello world task
Ok, so now that we’ve got the basics of rake down, let’s move on to doing something a little bit more useful. script/plugin is a great tool for grabbing and installing plugins from just about anywhere, but it doesn’t have an obvious way of deleting them (that I’ve found yet. Someone prove me wrong, please!). We could just modify script/plugin ourselves, but instead we’re going to write our own custom rake task to remove a plugin. There’s not much to it, but we’re going to expand it a little bit so that if the plugin directory is under source control through SVN or Git we’ll remove it from our repositories before we delete the actual files, so they don’t hang around. Starting at the top, we’re going to create a task called “remove and put it in a namespace called “plugins”. So let’s open an editor, and create a file in lib/tasks called plugin.rake. Inside plugin.rake we’re going to then add these lines first:
namespace :plugins do
desc "Removes an unwanted plugin"
task :remove do
end
end
After we save, let’s run the rake command to search for our new task to make sure it’s actually there. As an aside, I’m going to be doing this in very small, iterative steps with a lot of testing in between. I don’t know how you might test-drive a rake task, but if anyone has any information on it I would really appreciate it. Since I don’t posses it right now though, baby steps!
$ rake -T plugins:remove
(in .../projects/code/rails/engines_demo)
rake plugins:remove # Removes an unwanted plugin
Success! Now, the next thing we’re going to want to do is to make sure the user provides an argument that is the name of the plugin we’re looking for. To do that, rake adds any key-value pairs passed in on the command line to the ENV array that you get for free. We’re going to follow Occams Razor here and name our parameter for the plugins name that we want to remove “name”. This is how it would look on the command-line.
$rake plugins:remove name=our_great_plugin
Let’s open up our plugin.rake file and add a little bit of code to check if it exists, and if not prints out an error to the user. If it does, let’s confirm what the parameter is:
namespace :plugins do
desc "Removes an unwanted plugin"
task :remove do
unless plugin_name = ENV["name"]
puts “You must provide a plugin name”
else
puts “plugin_name = #{plugin_name}”
end
end
end
We now have two functional scripts to run to test our script (with and without a ‘name=’ parameter). Let’s verify our results:
$ rake plugins:remove
(in .../projects/code/rails/engines_demo)
You must provide a plugin name
$ rake plugins:remove name=demo_plugin
(in .../projects/code/rails/engines_demo)
plugin_name = demo_plugin
Success! Tomorrow we’ll continue with this and add the functionality to actually remove the plugin.