Let Go of the Banana!

There’s a story about how Pacific islanders catch monkeys. I’ve no idea if this is actually true, but it doesn’t actually matter for our purposes. Apparently what they do is they hollow out a coconut and tie the coconut to a tree. They ensure that the coconut has a single hole in it that is large enough for a monkey’s hand to go through. Then they take a banana and insert it into the coconut endways. The trap is set.

The story goes that along comes the monkey, smells the banana and reaches into the coconut, grabs the banana and is trapped. The monkey cannot pull its hand out while grasping the banana, and it won’t let go of the banana. Whether this is actually a great way to catch a monkey or not I have no idea, but it illustrates the idea of getting caught because you can’t let go. It happens quite a lot when I’m programming and yesterday gave me a great example.

Michael and I were pairing on extracting some project metrics from the ProjectScope system to use in AgileVentures projects listing. ProjectScope is a monolithic Rails app created by some Berkeley students. It has some nice features, but I don’t want to maintain that codebase particularly. I’m also thinking that breaking our own monolithic Rails projects into multiple gems connected via micro-services might make for a healthier project ecosystem for AgileVentures.

So anyhow, we got the tiny sliver of functionality necessary to grab code climate stats into a gem; well, I say gem, we didn’t publish it to RubyGems yet, but we added a gem spec so that we could pull it directly into our monolith using GitHub. I was all gung ho to publish it, but Michael demurred saying it didn’t have enough functionality yet, and rather than pushing the point I focused on getting my banana.

So what was my banana? Well making the CodeClimate badges gem was pretty straightforward and fast. The load bearing code is just this:

require ‘httparty’
class CodeClimateBadges
def initialize identifier
@identifier = identifier
end
def gpa_badge_url
@identifier.gsub!(/\/$/, ‘’)
https://codeclimate.com/#{@identifier}/badges/gpa.svg"
end
def gpa
response = HTTParty.get(gpa_badge_url)
stat_regex = /fill-opacity=”.3">.*?fill-opacity=”.3">([^<]+)/
response.body =~ stat_regex ? $1 : nil
end
end

CodeClimate’s experimental API is only available on paid plans, while their SVG badges are designed to be loaded over HTTP img tags onto thousands and thousands of GitHub pages. Our simple class provides two public methods. One to get a project’s CodeClimate GPA badge, and the other to grab the actual score from 0.0 to 4.0; based on the US grading system that I don’t really understand, but apparently a 4.0 is good. Under the hood this is rather brittle code and will break if CodeClimate updates their SVG layout, but it works for the moment. We drove the creation of the above with tests like this:

describe CodeClimateBadges
subject(:badges) { described_class.new 'github/AgileVentures/WebsiteOne' }
it ‘returns badge_url’ do
expect(badges.gpa_badge_url).to eq ‘https://codeclimate.com/github/AgileVentures/WebsiteOne/badges/gpa.svg' }
end
it ‘grabs gpa from the badge’ do
expect(badges.gpa).to eq ‘3.5’
end
# other tests omitted for brevity

Actually the test layout is a bit of a mess from my repeated attempts to get the banana, but initially it was a lovely experience to be working on a pure Ruby app again, just defining the simple interface it would have. Michael was keen to add a test to check for trailing slashes, which I thought was us getting off topic, but it took him 30 seconds to get it done, so I burned almost more time questioning doing it — doh!. In contrast to MY banana!

So we had this working, but I thought, we should be good net citizens here — this test of extracting the GPA is hitting CodeClimate’s servers — we should put in [VCR](https://github.com/vcr/vcr) to record the correct HTTP response once and then play it back in future test runs. That way our tests won’t hit the network. I tend to prefer VCR to [WebMock](https://github.com/bblimke/webmock), particularly for acceptance tests, since it captures a complete HTTP interaction in its entirety and can play it back, and can be updated to whatever the new HTTP interaction is (when and if it changes) by deleting the cassette and re-recording. With WebMock you have to write out the specific HTTP endpoint by hand which is kind of fiddly and sometimes important details get left out since it’s only a partial simulation of the HTTP interaction, making it more work to maintain.

At least that’s my story, but I’m starting to wonder. I’m also starting to wonder if in this context (as in stubbing Google Maps in LocalSupport using [PuffingBilly](https://github.com/oesmith/puffing-billy)) if when a company is providing a scaled service for public consumption, is it really critical to stub that out as a dependency? The good net citizen sounds sensible. Isolating your code from remote dependencies also sounds good — tests should run faster too. I know some others prefer WebMock. It makes strong sense for unit tests — there’s also an argument for dependency injection, but do we do that up front, or wait until we actually need to change that dependency — in our current case that’s HTTParty …

In retrospect doing a dependency injection of HTTParty and then stubbing that directly would have been faster than the VCR coconut from which I was trying to extract a banana. The banana/coconut thing for me in this case was VCR. I’ve set it up countless times before — used it over and over with Cucumber, and RSpec further in the past. Generally all good; but for some reason could just not get it to work with the current gem. We were following the instructions for [RSpec usage from the Relish docs](https://relishapp.com/vcr/vcr/v/3-0-1/docs/test-frameworks) but neither of the metadata or macro approaches would work. The cassettes directory was created, but no cassettes got created there from any of the permutations of config we (I) tried. I started taking other things apart, e.g. replacing HTTParty with HTTP::Net; pulling in the example VCR RSpec code to see if I could get a ground truth on what was going on, i.e. determine whether the problem was in VCR or in our setup. Before I’d even got so far I’d said to Michael this was a banana I was having trouble letting go of.

What’s the psychology there? Here’s something that I have got to work before, should be simple, that I think is good practice, and there’s this feeling that each change that I make is going to be the one that fixes things, but actually as time passes I’m spinning my wheels. To connect to another analogy its like you can’t put a frog in boiling water — it jumps out apparently — but if you put it in water at room temperature and gradually increase the heat … I was burning up more of our coding time thrashing on VCR.

We changed driver-navigator roles and Michael got started gemifying the code, and then creating a branch for the AgileVentures code base so we could show the GPA in the project listings. I still thrashed in the background distracting me from being a good navigator. I forced myself from the computer to get tea and a hot cross bun, and eventually got focused back on using the gem in our main project. That allowed us to get the functionality embedded there:

It’s not the proper code climate logo which doesn’t exist in font-awesome yet, and the code could use the extraction of a partial, but we got it working and into a [pull-request](https://github.com/AgileVentures/WebsiteOne/pull/1199) which I think was a really important step. Having been talked out of immediately publishing the gem, I returned to the VCR issue once the PR was done and wrote the thing up as a [VCR issue](https://github.com/vcr/vcr/issues/595) which I maybe should have done earlier before less thrashing.

Who knows what the correct strategy should have been. I guess I’m tempted to adjust the code to something like:

require ‘httparty’
class CodeClimateBadges
def initialize identifier, http = HTTParty
@identifier = identifier
@http = http
end
def gpa_badge_url
@identifier.gsub!(/\/$/, ‘’)
https://codeclimate.com/#{@identifier}/badges/gpa.svg"
end
def gpa
response = @http.get(gpa_badge_url)
stat_regex = /fill-opacity=”.3">.*?fill-opacity=”.3">([^<]+)/
response.body =~ stat_regex ? $1 : nil
end
end

I could be turning the instance vars into private attr_readers, but again, is that over anticipation of what’s needed?

Anyway, then the tests could be something like:

describe CodeClimateBadges
let(:http) { double(:http, get: ‘<text xmlns=”http://www.w3.org/2000/svg" x=”95" y=”15" fill=”#010101" fill-opacity=”.3">3.5</text>’) }
subject(:badges) { described_class.new ‘github/AgileVentures/WebsiteOne’, http }
it ‘returns badge_url’ do
expect(badges.gpa_badge_url).to eq ‘https://codeclimate.com/github/AgileVentures/WebsiteOne/badges/gpa.svg' }
end
it ‘grabs gpa from the badge’ do
expect(badges.gpa).to eq ‘3.5’
end

forcing our RSpecs to a more solid unit testing role, and then I could create a Cucumber acceptance test using VCR (where I have lots more working code examples to help when things go wrong).

Anyway I guess the coding paranoia will continue. All these different heuristics and “best” practices, and which ones are actually worth adhering to? One thing I feel confident of is that it’s at least worth pushing a little further to break up our monolithic rails app and get the different slivers of functionality into gems and micro services, if I don’t get too distracted by bananas along the way … :-)