Things I learned with Nynne last week, 1
For the next couple of months, I get to mentor Nynne, an incredibly curious and determined apprentice software developer. Mentoring is rewarding in many ways, not least because you learn the quirks of your craft. Here are the quirks we discovered this week.
Cloning via SSH vs HTTPS with Github
When Nynne cloned her first private repository, she wondered why she was getting asked for a username/password even though she had setup her ssh key. The default clone url on Github uses https, and when you clone a private repository, it asks you for your username:
Username for ‘https://github.com':
I never realised it means your Github username and password. I always thought “meh what is this”, and switched the clone url to be ssh, never thinking about what the https clone url is actually for. A friend pointed out that it is useful if you are behind a proxy that blocks ssh traffic.
Github help article on setting git up to remember your https password. But beware, if you have two-factor authentication set, you’ll need to create an access token.
ssh key fingerprints
To check that Nynne had the correct ssh key setup on Github, we had to compare keys using their fingerprint. I had never needed to check keys before because my keys just worked. I guessed that the representation Github shows is the key’s digital signature, or fingerprint, and we google’d how to get the fingerprint from the public key.
ssh-keygen -lf path-to-your-public-key (eg. ~/.ssh/id_rsa.pub)
The algorithm to create the fingerprint in this case is MD5. An example of why fingerprints are useful: imagine you ask your friend for their public key so you can encrypt a love letter to them. They send you their public key via email, and you phone them to verify they key. You could read out the whole key to them, which is ~370 characters or longer. Or you could just read the fingerprint, which is 48 characters.
There is a nice explanation of public key fingerprints on wikipedia. Remember, never upload/give out your private key, only your public key.
./ruby file inclusion
Nynne is building an api in ruby and wants to do Test Driven Development. Since she is using Sinatra, I helped her write a simple test suite using the unit testing library that comes with ruby because that is what the Sinatra examples use. We put the test in the a test directory, eg.
test/api_test.rb
And from the test file, I tried to include the api file with a require like this:
require “../api”
But it didn’t work and I didn’t immediately know why. It’s because when we ran the test, we ran it like this:
ruby test/api_test.rb
The require statement uses the working directory of the ruby process when resolving relative paths. The working directory is the directory that you ran the ruby process in. It is not the directory the file lives in.
Read here how you can find out how to access the working directory from within your ruby code.
Multi-line strings in ruby
In some programming languages, you cannot define a string over two lines, eg. this may not work:
foo = “hello
i am foo"
puts foo
But in ruby it does work. I was surprised!
MiniTest
I’ve always used rspec but the test examples in the Sinatra doc use the default testing library that comes with ruby. We copied the example tests but we couldn’t run them. We kept getting obscure errors about MiniTest, eg.
MiniTest::Unit::TestCase is now Minitest::Test. From /Users/duana/.rbenv/versions/2.1.3/lib/ruby/2.1.0/test/unit/testcase.rb:8:in `<module:Unit>’
/Users/duana/.rbenv/versions/2.1.3/lib/ruby/2.1.0/test/unit.rb:676:in `<class:Runner>’: undefined method `_run_suite’ for class `Test::Unit::Runner’ (NameError)
Turns out that since ruby 1.9, MiniTest entered the scene as ruby’s default testing library. require ‘test/unit’ provided a compatibility layer, but it doesn’t work in ruby 2.1.something (although suprisingly it does work in ruby 2.2.something). The change is explained nicely in this old blog post.
The Sinatra test example should be updated to have:
require 'minitest/autorun'
..
class HelloWorldTest < Minitest::Test
instead of:
require 'test/unit'
..
class HelloWorldTest < Test::Unit::TestCase
I’m working on a pull request ;)
Thanks to Nynne for discovering all these quirks and making me think more about them.