Tuesday, October 16, 2012

ActiveRecord, JSON & hashes

While testing a new script for some data loading, I found myself on our testing platform and stymied because the foundation things I needed were not part of that database.  I needed certain license records and Forge records.  While the staging system has a more or less complete copy of our production system, the testing system has a much more constrained data set.  This is a one of my bugaboos: making sure there is sufficient data for testing. And, once again, I questioned the decision to NOT use TDD for the script development.  That way, I could have mocked all the scenarios I needed and probably would have completed the script in less time to a greater level of quality.  But I digress.

I was in the console of the testing environment (not the "test" environment) and needed to have a particular license as part of the testing dataset.  I wanted to use the tools I had, not have to write a migration, start up PSQL, or any of those pathways.  I wanted to be lazy and get the record from the staging environment and copy it into the testing environment.

So, I found the license in the staging environment and converted it to json with license.to_json. That was wicked easy.  The next step was to parse the JSON, which will convert it to a hash, then extract the license value. That looks like this:

license_hash = JSON.parse(json)['license']

The result was a pretty little hash with all the necessary data that could be copied from one console to the next and then pasted into place.  In no time at all, I had the needed record in the testing database.

Wednesday, October 10, 2012

Nil Object in Reality Land

Recently I was reviewing some code with a colleague; we were looking at some new widget code.  We switched to the branch and started our server.  Looking at the first randomly selected project, everything looked good, so we copied the widget code and took a look at what the user would actually see.

Problemo.  The user was going to see a 500 message through the tiny view port of the widget.

The problem was that there was code looking for the filename of the project logo in order to display the logo.  However, this particular project did not have a logo.  So, project.logo (which was nil) caused a problem when the code then sought to access project.logo.thumbnail.  Boom.  No method "thumbnail" on NilObject.  heh.

Looking at the code, the obvious pattern would have been to do something like:

if project.logo && project.logo.thumbnail

But this is cowardly, timid code and I have been influenced by Avdi Grimm's thoughts on Confident Code. Once it was pointed out, it was clear that I was never very enamored with all those protective "if this and if that and if the other thing, then and only then do something".  I really like the idea of writing code that knows how to handle itself.

My colleague and I decided to address the issue by using a NullObject pattern. Examining the code, we saw that we could have a NullLogo object that responded to a "public_filename" method and if the project had no logo, it would return the NullLogo object instead of a Logo object.  The NullLogo#public_filename method returns "no_logo.png" and so we are always guaranteed to show something reasonable.

We eliminated quite a number of lines of timid code and were very pleased.

So we pushed our code onto the staging server and looked.  Yes, the empty logo behavior was correct. So I deleted a logo from an existing project, something that can easily be done by our users.  Boom.

Digging in we saw that the Logo derived from Attachment and Attachment did magic things.  Including using a gem that managed file sizing and thumbnail generation and pushing logos to S3.  A former colleague implemented clever logic to put files in the local filesystem during development and on S3 during production; all quite reasonable.

But, deleting the logo did not trigger the cascade of cleanup; the author of the gem had no support for cleaning up attachments and all the thumbnails it had created.  Now the database had data about attachments that did not exist and S3 had files of logos that no one would ever see or even want again.

It turns out that this is the behavior that has been present in the system for ages.  Our use of TDD and careful testing revealed this problem that no one has notice although this functionality has been in the product for a long time.

To wrap up the story; we implemented functionality to clean up the database and left it as a future exercise to clean up the logos from S3.  After all, these are a tiny fraction of the storage we use there, so let's not get side tracked by the bicycle shed when we have a nuclear reactor to build.

Tuesday, October 02, 2012

SSH to VirtualBox Guest in Mountain Lion

My development system is OS/x 10.8, Mountain Lion.  I run Ubuntu 10.04 LTS Server in a VirtualBox VM and all the development work is done in that server (easiest for the entire team to ensure we all have the same dev environment).

I switched over from a desktop system (Mac Pro) to a laptop (MacBook Pro), copied over my .vbi file with my Ubuntu server and something was different.  I had been ssh'ing into the VirtualBox guest without any issues.  Set up my ssh keys and an alias.  Sweet.  But, with the laptop, I couldn't get there from here.  The host name couldn't be resolved.

Yes, VB Network mode was Bridged Adapter.  At first, the issue was waiting for the DNS servers to refresh the IP address of the guest OS, which was now on a different subnet because I was connecting over Wi-Fi instead of Ethernet.

But, after closing the laptop for the night, the issue was back and I knew it wasn't from switching subnets.  A clue was that I couldn't also connect to one of our development utility servers.  Checked wireless -- default configuration had wireless connecting to the guest Wi-Fi and not the corporate Wi-Fi.  Fixed that, but no love.  Flushed the DNS cache and voila; ssh to the guest machine.

Here's the key: on Mountain Lion use:

sudo killall -HUP mDNSResponder

dscacheutils is still present, but doesn't work in all situations.  Use the mDNSReponder.