Perforce Driver for CruiseControl.rb

Posted by Ben on July 23, 2008

The past couple months we’ve been working on a contract for Intuit where we are using Perforce for our SCM. We needed to setup continuous integration but couldn’t find a fully functioning Perforce driver. With the help of Tom Hanely and Chris Bailey we successfully created a driver that works with Perforce and CruiseControl.rb. Check out the README file for instructions on getting started.

Besides updating the original driver to work with CruiseControl.rb 1.3, we also fully implemented all methods so that changesets and diffs will be accurate on the website and sent emails. Please let me know if you run into any errors or problems.

Bootstrap is coming…

Posted by Kevin on March 11, 2008

Bootstrap - Self employment is about to get easier

Self employment is about to get easier…

We have officially moved into private alpha on Bootstrap, our first major homegrown application. We aren’t saying much about it at this point while select users are banging away on it. But we can say this - it will definitely increase the time an entrepreneur can spend growing a business, rather than dealing with the hassles of running it. It’s entirely web-based and will work on all the major browsers…:-)

If you would like to learn more about Bootstrap, go to our home page at www.gobootstrap.com and submit your email. We’ll keep you posted as we open access to more folks and unveil specific features.

Mileage expense tracking, anyone?

Posted by Kevin on January 02, 2008

We released an update to Paybackable.com this morning that adds mileage expense tracking. It uses up-to-date IRS rates (now 50.5 cents per mile for 2008) to calculate how much you can get reimbursed for those business miles. Just enter the date of travel, a description (purpose of trip, destination, who you saw, etc.) and the miles driven and Paybackable will calculate the correct amount.

One other interesting note about this feature: it actually solves a gap in QuickBooks. Their vehicle mileage tracking feature doesn’t support reimbursing employees for their miles. To quote their support site: “You cannot use vehicle mileage tracking to reimburse your employees for mileage.” Their suggested solution is to create a bill payable to the employee (or vendor) with line items for the trips. This is precisely what Paybackable does, which is nice.

Check it out at www.paybackable.com

www.paybackable.com

Posted by Kevin on December 05, 2007

Well, we got frustrated enough with handling our own out-of-pocket expenses that we wrote a small app to help create online expense reports. It’s live at http://www.paybackable.com. It lets you track expenses and submit expense reports as IIF files for import into QuickBooks. It will also pull the expense categories from a QuickBooks chart of accounts IIF file so everything matches up. There are some nifty elements, like auto-complete of payee names with memorized categorization, plus keyboard shortcuts for quickly changing the date.

While it’s been very helpful for us (took the whole process from 20-30 minutes per expense report down to less than 5), we were wondering if anyone else might find it useful. Feel free to give it a try and let us know what you think. You can email me (Kevin) at esomnie dot com

HTTP Basic Authentication using restful_authentication with Rails 1.2

Posted by Ben on September 27, 2007

I ran into a small issue this morning with the restful_authentication plugin. We are developing an application where we need to provide a JSON data feed. The feed is protected using HTTP Basic Authentication. There is currently a bug in the plugin which makes Basic Authentication fail. When using the following curl command, I kept receiving a 406 http code from the server.

curl -X GET --basic -u bcurren:test http://localhost:3000/accounts.js

The reason for the 406 error was two fold: 1. there was a bug in restful_authentication which rejects all username and password combinations, even if they are valid 2. restful_authentication does not respond to js requests, so a 406 error is returned, meaning the request type is not valid.

To fix the bug in restful_authentication, I changed the following:

def login_required
  self.current_user ||= User.authenticate(username, passwd) || :false if username && passwd
  logged_in? && authorized? ? true : access_denied
end

to the following

def login_required
  if self.current_user == :false && username && passwd
    self.current_user = User.authenticate(username, passwd) || :false
  end
  logged_in? && authorized? ? true : access_denied
end

The bug is a result of :false being returned from current_user rather than false or nil. Since :false is a symbol it always evaluates to true when used in conditions. The updated code explicitly test if the current_user == :false

After the bug fix above, I was able to successfully retrieve the JSON feed when supplying a valid username and password. However, when submitting and invalid username and password, the application was return a 406 code rather than a 401. This does not result from a bug in restful_authentication but from a lack of support for js requests out of the box. I simply added the following to the access_deniedmethod in authentication_system.rb.

  accepts.js do
    headers["Status"]           = "Unauthorized"
    headers["WWW-Authenticate"] = %(Basic realm="Web Password")
    render :text => "Could't authenticate you", :status => '401 Unauthorized'
  end

I hope this post helps anyone who is tackling the similar issue in Rails 1.2. I will post about some of the other trail and tribulations of using the to_json method in Rails 1.2 in the near future.

Connection refused error when using Capistrano 1.4.1 gateway feature

Posted by Ben on July 28, 2007

The other day we were setting up Capistrano to tunnel through a gateway when deploying our web application. For those who don’t know about this feature of Capistrano, it allows you to deploy your web application to servers with private ip addresses behind a firewall. To configure the gateway feature, make the following changes in your deploy.rb file.

set :gateway, "gateway.esomnie.com"
role :web, "10.5.1.102"
role :app, "10.5.1.102"
role :db,  "10.5.1.202", :primary => true

The gateway server, gateway.esomnie.com, has a public ip and is accessible via ssh; while, web, app, and db server are located behind a firewall and all have private ips. Capistrano allows you to tunnel through the gateway server to deploy on the servers with private ips. After we updated the configuration file I ran cap deploy. After running deploy, we received the following error.

Errno::ECONNREFUSED
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh/transport/session.rb:89:in `initialize'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh/transport/session.rb:89:in `open'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh/transport/session.rb:89:in `initialize'
/usr/local/lib/ruby/1.8/timeout.rb:48:in `timeout'
/usr/local/lib/ruby/1.8/timeout.rb:76:in `timeout'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh/transport/session.rb:88:in `initialize'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh/transport/services.rb:121:in `new'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh/transport/services.rb:121:in `register_services'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:42:in `call'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:42:in `call'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:40:in `synchronize'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:40:in `call'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/service-point.rb:122:in `instance'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/container.rb:308:in `[]'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh/connection/services.rb:60:in `register_services'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:42:in `call'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:42:in `call'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:40:in `synchronize'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:40:in `call'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/service-point.rb:122:in `instance'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/container.rb:308:in `[]'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh/service/process/services.rb:53:in `register_services'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:42:in `call'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:42:in `call'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:40:in `synchronize'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/lifecycle/singleton.rb:40:in `call'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/service-point.rb:122:in `instance'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/container.rb:308:in `get'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/container.rb:380:in `method_missing'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh/service/services.rb:42:in `register_services'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/container.rb:365:in `__send__'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/container.rb:365:in `require'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/definition-context.rb:77:in `__send__'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/definition-context.rb:77:in `require'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh/session.rb:130:in `initialize'
/usr/local/lib/ruby/gems/1.8/gems/needle-1.3.0/lib/needle/container.rb:107:in `define'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh/session.rb:106:in `initialize'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh.rb:47:in `new'
/usr/local/lib/ruby/gems/1.8/gems/net-ssh-1.1.1/lib/net/ssh.rb:47:in `start'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/ssh.rb:33:in `connect'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/gateway.rb:80:in `connect_to'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/gateway.rb:74:in `initialize'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/gateway.rb:74:in `new'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/gateway.rb:74:in `connect_to'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:527:in `establish_connection_to'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:527:in `initialize'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:527:in `new'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:527:in `establish_connection_to'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:520:in `establish_connections'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:520:in `map'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:520:in `establish_connections'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:553:in `execute_on_servers'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:207:in `run'
./config/deploy.rb:96:in `load'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:159:in `instance_eval'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:159:in `before_update_code'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:155:in `send'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:155:in `update_code'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/recipes/standard.rb:145:in `load'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:430:in `transaction'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/recipes/standard.rb:144:in `load'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:159:in `instance_eval'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:159:in `update'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/recipes/standard.rb:178:in `load'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:159:in `instance_eval'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:159:in `deploy'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/cli.rb:268:in `send'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/cli.rb:268:in `execute_recipes!'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/cli.rb:268:in `each'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/cli.rb:268:in `execute_recipes!'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/cli.rb:239:in `execute!'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/cli.rb:12:in `execute!'
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/bin/cap:11
/usr/local/bin/cap:16:in `load'
/usr/local/bin/cap:16
/usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/gateway.rb:92:in `connect_to': Could not establish connection to 10.5.1.102 (RuntimeError)
        from /usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:527:in `establish_connection_to'
        from /usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:527:in `initialize'
        from /usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:527:in `new'
        from /usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:527:in `establish_connection_to'
        from /usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:520:in `establish_connections'
        from /usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:520:in `map'
        from /usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:520:in `establish_connections'
        from /usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/actor.rb:553:in `execute_on_servers'
         ... 19 levels...
        from /usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/cli.rb:12:in `execute!'
        from /usr/local/lib/ruby/gems/1.8/gems/capistrano-1.4.1/bin/cap:11
        from /usr/local/bin/cap:16:in `load'
        from /usr/local/bin/cap:16
*** [before_update_code] transaction: rollback
  * [before_update_code] rolling back

After debugging for a couple of hours and becoming familiar with the Capistrano code base, we found the source of the error. For this particular site, we are running ssh on a non-standard port of 2222 on the gateway. I needed to add ssh_options[:port] = 2222 to deploy.rb to accommodate the non standard port. However, this configuration option causes a bug in Capistrano when using the gateway feature and this is the cause of the failure above. In order to solve the issue, I wrote a small patch to fix the problem in Capistrano 1.4.1. If you are running into the same issue, download the patch and run the following command to patch your Capistrano 1.4.1 installation.

% cp capistrano-141diff.gz capistrano1.4.1_home
% gunzip capistrano-141diff.gz
% patch -p1 < capistrano-141.diff
patching file lib/capistrano/ssh.rb
patching file test/ssh_test.rb

That should fix the bug in Capistrano and when you run cap deploy, it should run successfully now. I have emailed Jamis Buck and will get the patch incorporated in future versions of Capistrano 1.x. I haven’t had anytime to review Capistrano 2.0 and see if this bug exists there as well.

Thinking outside the box

Posted by Ben on July 10, 2007

Early today I was talking with Dean, our new Web Developer, about how I started hacking in Ruby. Like many Ruby hackers these days, I started off using Rails and then became more and more interested in Ruby. When I developed my first small Rails web application it was rather apparent at how much faster I could write clean, testable code. I also immediately began to wonder, how does Rails perform all of its magic?

The natural place to start looking was in the Rails source code. I was immediately impressed at how dynamic Ruby was. After I started to grasp the code base and some of the patterns, I started to fall in love with Ruby. The power of Rails lies in Ruby. I know there are plenty of other beautiful languages out there, but my background was mainly with c, C++, C#, Java, and PHP, so some of the Ruby features were quite new to me. After learning Ruby, I realized that my whole career, I was solving problems in a box. My problem solving and design skills were constrained by the programming language at my disposal. The dynamic power of Ruby opened that box and I now tackle problems in a new light. The more Ruby code I read the more my toolset grows and the more ways I can look at solving a problem.

I keep thinking, what will the next programming language be that changes the way I think this much? Is it something I can even imagine right now? It’s strange how paradigms shape our world and make it so difficult to think outside the box.

Using url_for in Ruby on Rails tests

Posted by Ben on May 04, 2007

On a recent Ruby on Rails project, we ran into a situation where we needed to use the ActionController::Base url_for method in our tests. My first attempt was to simply write the test as follows:

def test_next__should_show_step3
  @request.env["HTTP_REFERER"] = @controller.url_for(
    :controller => 'wizard',
    :action => 'step',
    :step_number => "1",
    :id => 1)
 
  post :next, :id => 1
  assert_not_nil
  assigns(:virtual_tour)
  assert_response :redirect
  assert_redirected_to :action => 'step', :step_number => "3", :id => assigns(:virtual_tour).id
end

When I ran the test it resulted in the following error:

test_next__should_show_step3(VirtualTourWizardControllerTest):
NoMethodError: You have a nil object when you didn't expect it! The error occured while evaluating nil.rewrite
/usr/local/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/base.rb:488:in `url_for'
./test/functional/virtual_tour_wizard_controller_test.rb:91:in `set_referer_step'
./test/functional/virtual_tour_wizard_controller_test.rb:64:in `test_next__should_update_virtual_tour'

When we looked at the rails code, we saw that an instance variable @url was not initialized yet in our tests. This was causing the nil error seen above. We found a post that wrote about a work around by initializing this variable by calling get :index or some other action that doesn’t cause a side effect. This seems like a bit of a hack so we dug a little further.

After a little digging in the rails source code, we found the class that does the actual URL rewriting. The class is called ActionController::UrlRewriter which url_for delegates to to do the actual URL rewriting. So we created a little helper method in test_helper.rb to wrap ActionController::UrlRewriter in a nice helper method called url_for. The following is the method we created in test_helper.rb

def url_for(options)
  url = ActionController::UrlRewriter.new(@request, nil)
  url.rewrite(options)
end

We can know rewrite our test to look like this.

def test_next__should_show_step3
  @request.env["HTTP_REFERER"] = url_for(
    :controller => 'wizard',
    :action => 'step',
    :step_number => "1",
    :id => 1)
 
  post :next, :id => 1
  assert_not_nil
  assigns(:virtual_tour)
  assert_response :redirect
  assert_redirected_to :action => 'step', :step_number => "3", :id => assigns(:virtual_tour).id
end

We ended up doing a little more refactoring after this since we set the referer like this in many of our other tests.