Archive for July, 2009
Grails TDD simplified
Let me start by saying that Testing Grails sucks. This is not to say that there aren’t supporting tools and technologies. There is super classes GrailsUnitTestCase, GroovyTestCase etc. and there is few solutions for mocking needs. However, in general, the cycle of writing a failing test, execute to fail, some code to make is pass, execute to verify passed and refactor takes a long time. This “long time” phrase is relative and I mostly compare with folks coming from plain-old Java world or from Ruby world. I have experience with both and grails is slow relative to both.
Of course there is some progress being made so that you are able to run specific test class using following command:
# specify <Class> without the trailing "Tests" $ grails test-app [-unit | -integration ] <Class>
The above command makes is so that you don’t have to run entire test suite. This still leaves a few things desirable:
- The user still have responsibility to specify -unit or -integration depending on which test is being run. The framework doesn’t figure this out automatically.
- The above command won’t fail is the specified test class is not found.
- When the test fails, the stack trace is long, long, very long. It takes pathetically long time to find the line that points to the problem in your test.
So, I wrote following script (in Ruby!) to resolve above issues.
Script: gtest
#!/usr/bin/ruby if ARGV.length != 1 puts "Expected 1 argument." exit -1 end test_name=ARGV[0] unit_or_int="" test_file_found=false if File.exists?("test/unit/#{test_name}Tests.groovy") test_file_found=true unit_or_int="-unit" elsif File.exists?("test/integration/#{test_name}Tests.groovy") test_file_found=true unit_or_int="-integration" else puts "No Unit or Integration test found for #{test_name}" exit -1 end testfile="test/reports/plain/TEST-#{test_name}Tests.txt" #system("touch #{testfile}") cmd="touch #{testfile}; (grails test-app #{unit_or_int} #{test_name} &); tail -f #{testfile} | filter_groovy_trace" puts cmd exec(cmd)
Script: filter_groovy_trace
grep -Ev "at org.codehaus.|at groovy.lang|at gant.Gant|at TestApp_groovy|--Output from test|--------|at org.gmock.internal|^Testcase: |at sun.reflect| at net.sf.cglib|at java.lang.reflect"
With these two scripts in my path all I need to do is:
gtest <Class>
I have been using it for over a month and it has served me well. It still doesn’t come close to what you could do in Java (execute individual test from inside IDE) or ruby (the java runtime initialization takes for ever compared to ruby runtime), but it reduces the pain considerably.
There is one more thing we can do to make it faster. Take out the xsl transformation at the end of test execution. I couldn’t find the command line option for that. I know it exists.
Reduced Pain => Enjoy!
Executing Mysql command on remote machine using Capistrano
On a project recently, I needed to frequently clear out some database table on staging server. Since I was going to need it occasionally for the foreseeable future, I wanted to add a capistrano task. Capistrano is good for this kind of stuffs, however what made this one interesting is the fact that the mysql command doesn’t take password on the command line, for obvious reasons. This was tricky until I remembered that deprec gem does offer ability to create databases on remote machine.
Crack opened the deprec gem and found interesting solution. Here is how one of the mysql task looks like inside deprec:
desc "Create a database" task :create_database, :roles => :db do cmd = "CREATE DATABASE IF NOT EXISTS #{db_name}" run "mysql -u #{mysql_admin_user} -p -e '#{cmd}'" do |channel, stream, data| if data =~ /^Enter password:/ channel.send_data "#{mysql_admin_pass}\n" end end end
So, I ended up doing this:
task :delete_confs do cmd = "delete from <table>; select count(*) from table" run "mysql -h <host> -u <user> -p <database> -e \"#{cmd}\"" do |channel, stream, data| if data =~ /^Enter password:/ channel.send_data "pword\n" end puts data # data gets updated after mysql command executes! end end
This turned out all working great.
Another interesting thing I found is that the “data” variable inside the block is kept updated with latest output from the ssh command. I could not have guessed this initially since you would expect it to be statically holding “Enter password:” and that’s it for the length of block.
Capistrano uses Net::SSH underneath so I am guessing there could be more options for even lower level access. And, yes there may be bash shell solution to this entire problem. The goal here was to create a capistrano task which fits will with ruby/rails development in general.
Fun stuff!
Converting all .html.erb views to .haml
After trying haml and sass on a few occasions, I am sold. Recently, I needed to convert all my /app/views/**/*.html.erb views into /app/views/**/*.haml and I ended up writing a rake task as follows:
require 'haml' require 'haml/exec' namespace :haml do task :convert do Dir.glob("app/views/**/*.html.erb").each do |html| puts html + "..." Haml::Exec::HTML2Haml.new([html, html.gsub(".html.erb", ".haml")]).process_result File.delete(html) end end end
This did the trick. There was only one minor thing I had to fix with the new *.haml files. The blocks with an “<%= end %>” were converted into “- end”. Instead the correct format was to not use “- end” and properly indent the code inside block.
So, for example, a .html.erb code as follows:
<% form_for @booking do |f| %> <%= f.error_messages %> <%= f.label :booking_reference %> <%= f.text_field :booking_reference %> <%= f.label :remarks %> <%= f.text_area :remarks %> <p> <%= f.submit "Submit" %> </p> <% end %>
was converted to:
- form_for @booking do |f| = f.error_messages = f.label :booking_reference = f.text_field :booking_reference = f.label :remarks = f.text_area :remarks %p = f.submit "Submit" - end
Instead, it should have been:
- form_for @booking do |f| = f.error_messages = f.label :booking_reference = f.text_field :booking_reference = f.label :remarks = f.text_area :remarks %p = f.submit "Submit"
Once I fixed this manually (and it could be a lot if you have lots of files), it all worked as expected.
Behavioral Economics 2: Sales tactics for the downturn
WSJ had a recent article on how a luxury wrist-watch seller manages to sell expensive watches in such times of recession. A few good tips, that apply to other fields in some form or another are:
- If customer asks for price of an item, say “the *value* is $xx”. Use the word “value” instead of “price”. Fair enough.
- If customer shows interest in one of your products, do not ask yes/no type question. Yes/No question has at most a 50/50 chance of going positive. Instead, invite them to try. In short, don’t offer them an easy way out.
- Focus on decision maker, don’t ignore others. With regard to watch purchase, the article talks about keeping the wife occupied while husband is looking to buy expensive watch. If wife is left-out, she might feel bored and will look for a way out.
- Make necessity and benefits of your product apparant. Keep their current (old) watch in between 2 brand new watch, to signify it is time to upgrade. Sneaky but, I guess, it can work.
Atleast some of these should apply to software consulting also. Nice article indeed.
Behavioral Economics: understanding human reasoning for marketing and sales
Business Week’s latest issue has an article on how companies analyze human reasoning and behavior to increase marketing and sales. It talks about 2 things:
- Present Bias: People are more inclined to focus on immediate hassles of changing a habit than future benefit. To me, this also explains the laziness which prevents most of us from achieving big results, on personal or professional level.
- Loss Aversion: People work harder to avoid losses than to pursue gains. This led the company to change the words in their advertisements from “save money” to “stop wasting money”. Again, very true in day to day context. We start doing exercise after encountering major disease, not before. It is a “loss aversion” after we have already encountered disease where as it was just a “potential benefit” before then.
My takeaway: Restate my “future-benefit” goals into a “loss-aversion” goals and, may be, I will have a better chance of achieving it
Ruby gotcha: defining class in ruby
Coming from Java this may be a little surprise that order of class initialization (order of method definitions) does impact the outcome of your program, as I just found out.
For example, consider 2 cases where I define a singleton class:
Case-1:
class A @@instance = A.new def self.instance @@instance end attr_reader :x private def initialize @x = 99 end end
Output:
>> A.instance.x => nil
Case-2:
class A private def initialize @x = 99 end @@instance = A.new public def self.instance @@instance end attr_reader :x end
Output:
>> A.instance.x => 99
The location of constructor/initialize() is different. And as we can see from output, the our constructor isn’t yet defined when A.new() was called in the first case and hence the @x instance variable didn’t get initialized the way we would have expected it to. The execution doesn’t complain since it uses the default/inbuilt constructor until our constructor gets defined.
Now I know.
Producer/Consumer using beanstalkd in Ruby
Got basic queueing to work in ruby using beanstalkd/beanstalk-client.
Producer.rb
#!/usr/bin/env ruby require 'rubygems' require 'beanstalk-client' beanstalk = Beanstalk::Pool.new(['localhost:11300']) while(true) do sleep(10) puts "posting message ..." beanstalk.put(Time.now.to_s) end
Consumer.rb
#!/usr/bin/env ruby require 'rubygems' require 'beanstalk-client' beanstalk = Beanstalk::Pool.new(['localhost:11300']) loop do job = beanstalk.reserve puts job.body job.delete end
Commands:
$ beanstalkd -d -p 11300 $ ./producer.rb # consumer in a different terminal $ ./consumer.rb
Enjoy!
Installing beanstalkd on ubuntu
Here is a commad dump for installing beanstalkd on ubuntu.
$ wget http://xph.us/dist/beanstalkd/beanstalkd-1.3.tar.gz $ tar zxvf beanstalkd-1.3.tar.gz $ rm beanstalkd-1.3.tar.gz $ cd beanstalkd-1.3/ $ ./configure
You might run into following error:
configure: error: Unable to locate libevent headers, please use --with-event=DIR
If so, install "libevent-dev" package.
$ sudo apt-get install libevent-dev
..continue..
$ ./configure $ make $ sudo make install
At this point, beanstalkd should be installed in proper location. Verify:
$ which beanstalkdEnjoy!
Installing mysql based gems on ubuntu
When intall Datamapper’s mysql adapter, do_mysql gem you man run into installation failure such as:
/usr/bin/ruby1.8 extconf.rb install do_mysql
checking for mysql_query() in -lmysqlclient… no
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers. Check the mkmf.log file for more
details. You may need configuration options.
Solution:
$ sudo apt-get install libmysqlclient16-dev
Generating hashed passwords from commandline
ruby -r ‘digest/sha1′ -e ‘puts Digest::SHA1.hexdigest("password")’