![]()
![]()
I'm Maxim Chernyak aka Hakunin. Every day I deal with three technologies - Ruby on Rails, Drupal, and Zend Framework. I deal and I blag. Natalie is my girlfriend and I love her.
Link: github.com/maxim/doer
Doer is a tiny procedural script for running tasks and commands on multiple subdirectories. It is very useful if you’re creating multi-app projects, like SOA (Service Oriented Architecture) projects.
I purposefully didn’t want to make it into a gem. This project is meant to be a very small hackable tool with all source at fingertips. It won’t grow and develop much from where it is now, as it already has everything it was meant to have.
Download "do" and "dofile_sample" into your root directory which contains all your project subdirectories.
chmod +x do
mv dofile_sample dofile mate dofile
Customize your dofile. (See Usage)
When finished - run commands using
./do command-name [project-name] [arg1, arg2, ...]
Let’s say you have two applications in your project: foo and bar. They’re in their own subfolders.
We shall create the dofile and start making PROJECTS hash.
#!/usr/bin/ruby HERE = File.dirname(__FILE__) PROJECTS = { :foo, :bar }
Ok, got our setup, now it’s command-making time. First thing - let’s make a "start" command.
PROJECTS = { :foo => { :start => "script/server -p6500 -d" }, :bar => { :start => "script/server -p6600 -d" } }
The above code will allow me to run the following shell commands.
./do start ./do start all ./do start foo ./do start bar
The first two lines are identical in function. There are some cases where "all" is required, but mostly it can be omitted when targeting all subfolders.
Let’s write the stop command now.
PROJECTS = { :foo => { :start => "script/server -p6500 -d", :stop => "mongrel_rails stop -P tmp/pids/mongrel.pid" }, :bar => { :start => "script/server -p6600 -d", :stop => "mongrel_rails stop -P tmp/pids/mongrel.pid" } }
Now you notice that :stop is identical for foo and bar. It’d be great to keep it DRY. That’s why there’s :default section.
PROJECTS = { :default => { :stop => "mongrel_rails stop -P tmp/pids/mongrel.pid" }, :foo => { :start => "script/server -p6500 -d" }, :bar => { :start => "script/server -p6600 -d" } }
:default is a fallback section. It’s only used if a project doesn’t have its own implementation of the requested command.
Time to write :restart. Restart is essentially stop and start. There’s certainly an easy way to do this.
PROJECTS = { :default => { :stop => "mongrel_rails stop -P tmp/pids/mongrel.pid", :restart => [:stop, :start] }, :foo => { :start => "script/server -p6500 -d" }, :bar => { :start => "script/server -p6600 -d" } }
That’s all! Now you have:
./do restart ./do restart foo ./do restart bar
Convenient.
Let’s talk about a more interesting case. You want to be able to commit multiple project into your repository, using the same commit message for all. Easy.
PROJECTS = { :default => { :stop => "mongrel_rails stop -P tmp/pids/mongrel.pid", :restart => [:stop, :start], :gc => ["git commit -am \":message\"", %w(message)] # <= this is the new task }, :foo => { :start => "script/server -p6500 -d" }, :bar => { :start => "script/server -p6600 -d" } }
As you can see - we created a :gc command. The ":message" string will be replaced by the first param in the command line. The array basically says that first param is called "message", which allows us to place a :message token in the string. Now we can do the following.
./do gc all "Some commit message" ./do gc foo "Some commit message" ./do gc bar "Some commit message"
Notice: in this case it’s required to use "all" to indicate we want to run across all projects. Otherwise script will think that
"Some commit message" is the name of the project.
Last but not least, you can use Procs. Let’s say you want a task that will verify if a host is running. Let’s pass a proc.
For this we will need a few requires.
require 'rubygems' require 'uri' require 'net/http' PROJECTS = { :default => { :stop => "mongrel_rails stop -P tmp/pids/mongrel.pid", :restart => [:stop, :start], :gc => ["git commit -am \":message\"", %w(message)] }, :foo => { :start => "script/server -p6500 -d", :status => Proc.new { begin message = "Foo Server (localhost:6500)".ljust(35) Net::HTTP.get(URI.parse('http://localhost:6500')) rescue puts message + "OFF" else puts message + "RUNNING" end } }, :bar => { :start => "script/server -p6600 -d", :status => Proc.new { begin message = "Bar Server (localhost:6600)".ljust(35) Net::HTTP.get(URI.parse('http://localhost:6600')) rescue puts message + "OFF" else puts message + "RUNNING" end } } }
You can DRY-up the last example by putting helper methods into dofile.
That’s all!
I wouldn't usually blog about this kind of event, but this cake made it necessary.
My fiancé has done tremendous job to organize a surprise party for me. Among other things, she ordered this incredible custom-made xbox 360 controller cake.

Oh boy, I randomly decided to test commenting here, only to realize that an error occurs when trying to submit one. I fixed it, so please, comment!
This is the way to get rid of 'nil.tmp_dh_callback=' bug by using Rails plugin. Whenever they fix this in rubyforge gem - simply remove the plugin directory from vendor/plugins.
cd rails_app/vendor/plugins mkdir rubyforge_patch cd rubyforge_patch mate init.rb
Paste this into init.rb
require 'net/http' class Net::HTTP def use_ssl= flag self.old_use_ssl = flag @ssl_context.tmp_dh_callback = proc {} if flag end end if Net::HTTP.public_instance_methods.include? "old_use_ssl="
That's it. You're good to go. No more nil.tmp_dh_callback error.