Well, to the extent that Bill and I recall things done literally days ago, back then we cleaned up the code a bit and made no substantial functional changes. So we still have these stories:

  • Separate out the folder names and passwords for production vs test;
  • FTP the category folders’ contents to the FTP site;
  • Test what it’ll do on the actual site, preferably without bringing down ronjeffries.com;
  • Decide how to trigger a build remotely and how not to do it again until needed;
  • Set up the file watcher things to do that.

Before we start, we’ll look at the code and tests … because that’s how we really decide what to do next.

We find this test to be hideous because of that horrible string we’re testing for:

  def test_what_will_be_ftped
    jr = set_up_for_jekyll_testing
    jr.move_ipad_files
    list_of_allegedly_moved_things = jr.ftp_the_files(simulated = true) 
    assert list_of_allegedly_moved_things.include? "/Users/ron/programming/test-ftp/test_jekyll_site/_site/articles/subfolder/index.html to something/subfolder/index.html"
  end

We just wanted some indication of what would be done and went to a long text string we could compare on. I’d hate to leave this lying around for an unsuspecting me of the future. So we’ll try to think about that as we go forward but we’re not going to prioritize it.

After nearly a half hour of reviewing the code, we decide to focus on this:

class JekyllRunner
  def initialize(ipad_folder, jekyll_folder)
    @ipad = ipad_folder
    @jekyll_folder = jekyll_folder
    @ftp_host_name='localhost'
    @ftp_folder='programming/test-ftp/_target/articles'
  end

This code reminds us that the JekyllRunner currently has the built-in knowledge of how to connect to the test output folder. For production, this needs to be a parameter of some kind – and in fact it is one of the reasons for the refactoring we did last time.

And somehow that reminds me, right in the middle of all this, of the ensure feature of Ruby, which we should be using in our “Execute Around Method”. Let’s do that before we forget, moving from this:

  def perform_in_folder(new_dir, &block)
    pwd = Dir.pwd
    Dir.chdir(new_dir)
    result = block.call
    Dir.chdir(pwd)
    return result
  end

Which should “really” be:

  def perform_in_folder(new_dir, &block)
    begin
      pwd = Dir.pwd
      Dir.chdir(new_dir)
      result = block.call
    ensure
      Dir.chdir(pwd)
    end
    return result
  end

Our return bug has been made impossible another way, so we can’t put it back in to see if this would have made it not happen but since our tests run I’m inclined to be happy. We have a brief discussion about whether all that stuff belongs in the begin and decide that it does.

Digression or opportunism?
You decide.

This is interesting, I claim. Right in the middle of of looking at one thing, and just about setting out to do it, we’re reminded of another issue. We jump immediately and do it. That’s one way. It was a quick change, we just made it and ran the tests. Because I was writing this article as we went, there was no chance of losing track of the plan. Had we been working more normally, we might have written some notes on cards. And, had we been actually in the middle of things, we’d probably have written this idea on a card and kept on with the other.

We don’t want to get distracted and never make any progress, and we certainly don’t want to set off on some distraction and fall into some rat hole full of yaks needing shaving. So it’s important to pay attention to what’s going on, but it’s also important, when a quick idea comes up, to be prepared to deal with it. In this case, the idea was tangent to what we were doing. Last time, we followed our noses along a trail of smelly code.

I think the main idea is not to be too rigid, but not to be too loose either. There are many objectives in play at once. We can’t put too many on the table at once, but maybe we can have a couple going.

How much digression we tolerate is very much a function of the particular programmer or pair. I recall pairing in Smalltalk with amazing programmers, such as Kent Beck or Ward Cunningham. They’d build a cascade of windows as they went. If something new came up, they’d pop a window on top and work on it, close it, and the next window to pop up would remind them what was going on before. Or they’d tuck the idea window down in the cascade a bit, knowing it would pop to the top later. Very smooth and adept. I dream of being that good, but I can sometimes handle two ideas on the table at the same time. Your mileage will vary.

Anyway, that’s done, so moving right along, now let’s see about parameterizing the JekyllRunner. We just add new parameters for the host name and folder:

  def initialize(ipad_folder, jekyll_folder, host_name, host_folder)
    @ipad = ipad_folder
    @jekyll_folder = jekyll_folder
    @ftp_host_name = host_name
    @ftp_folder = host_folder
  end

This should fail the current tests nicely, since we’re not providing those parameters yet and it does, whining about “wrong number of arguments (2 for 4)”.

Hold on, Ron, isn’t this bass-ackwards?

Aren’t you supposed to write a failing test first? Well, yes, if you’re some kind of robot. Here, I broke the implementation, by adding those parameters to initialize, which broke a test. Since the test is the only creator of a JekyllRunner, doing it this way seemed OK to me. If you prefer a more strict test-then-code style, go for it. I’m here to show you what I do, and what happens, good or bad, when I do it.

Anyway, now we have a failing test, and it’s the test that’s wrong, so we fix the test:

  def set_up_for_jekyll_testing
    FileUtils.rm_rf("./_target")
    FileUtils.mkdir("./_target")
    FileUtils.mkdir("./_target/articles")
    FileUtils.rm_rf("./test_jekyll_site/articles")
    ipad_folder = '/Users/ron/Dropbox/_source'
    jekyll_folder = './test_jekyll_site'
    return JekyllRunner.new(ipad_folder, jekyll_folder, 
      'localhost', 'programming/test-ftp/_target/articles')
  end

This is the “Extract Explicit Parameter to Obscure Distant Method” pattern, according to Tozier. I can see his point. But it’s the right thing to do.

We continue to search for explicit strings in JekyllRunner. This following example of the literal articles is OK, I claim, because I choose as the Customer / Product Owner not to parameterize where the articles are:

  def move_ipad_files
    FileUtils.cp_r("#{@ipad}/.","#{@jekyll_folder}/articles")
  end

That’s a judgment call. Arguably “You’re Gonna Need” the ability to parameterize the bottom level folder. As constant readers know, I generally try not to respond to “Gonna Need”, so we move on. Then there’s this:

  def ftp_connected_to_target
    ftp = Net::FTP.new(@ftp_host_name)
    ftp.login(Passwords::USER,Passwords::PASSWORD)
    ftp.chdir(@ftp_folder)
    ftp
  end

We discuss whether the host and folder should really be member variables here, and I bludgeon Tozier until he agrees that they should. In that context, then, the password hookup looks odd. Should those be passed in by the creator?

The situation now is that only the JekyllRunner looks at the passwords:

require "net/ftp"
require "./passwords"

The passwords file is local to the instance of JekyllRunner that we’re running, that is, it is inside the test folder for our tests, and it’ll be inside my real site source folder in production. So maybe this is OK. But … (says Tozier) … should we ever wish to run these tests in the real site source folder, we’d need the testing passwords, and if we want it to run in production, we’ll need the real passwords, so just looking in the root of the source for them will not do.

This argues for what? At first we think it argues for passing the user id and password into the creation method. But that exposes the password file to another program, the tests, and potentially to another one, whoever creates and runs the JekyllRunner. That seems bad.

Since both the tests and our still-to-be-written main program need different passwords in the real folder, we could either use two different file names, e.g. test_passwords and real_passwords, or we could use a single file name with four entries in it, for test_user, real_user, and so on.

In fact, if we make that change to the passwords file, we can simply create our JekyllRunner with a string parameter "TEST_" or "PROD_" and leave it to him to read the right one. This keeps the password file limited to just the guy who uses it. That seems better.

Our solution is this:

  def set_up_for_jekyll_testing
    FileUtils.rm_rf("./_target")
    FileUtils.mkdir("./_target")
    FileUtils.mkdir("./_target/articles")
    FileUtils.rm_rf("./test_jekyll_site/articles")
    ipad_folder = '/Users/ron/Dropbox/_source'
    jekyll_folder = './test_jekyll_site'
    return JekyllRunner.new(ipad_folder, jekyll_folder, 
      'localhost', 'programming/test-ftp/_target/articles', 'TEST_')
  end

Note that we’re just passing in the prefix 'TEST_'. Putting that together with this gets us running:

class JekyllRunner
  def initialize(ipad_folder, jekyll_folder, host_name, host_folder, prefix)
    @ipad = ipad_folder
    @jekyll_folder = jekyll_folder
    @ftp_host_name = host_name
    @ftp_folder = host_folder
    @id_prefix = prefix
  end

  def ftp_connected_to_target
    host = Object.const_get('Passwords::' + @id_prefix + 'USER')
    pass = Object.const_get('Passwords::' + @id_prefix + 'PASSWORD')
    ftp = Net::FTP.new(@ftp_host_name)
    ftp.login(host, pass)
    ftp.chdir(@ftp_folder)
    ftp
  end

This is a bit weird, because we have to use Ruby’s reflection capability to convert our string-calculated names to actual constants. The above seems to be how you do that. We resisted (well, one of us resisted) doing any prints along the way, we just let the test failures guide us.

Here’s what that passwords file looks like, in case you were wondering:

module Passwords
  TEST_USER = 'some_user'
  TEST_PASSWORD = 'some_password'
  PROD_USER = 'some_real_user'
  PROD_PASSWORD = 'some_real_password'
end

Tozier says something that reminds me that we could, instead, set up some kind of hash value, with a single constant name, and then compute the key and fetch the value, avoiding the more obscure use of reflection. I describe the idea’s origin this way because he had some other idea which I never really understood and thought he meant the above. So who gets the credit for that idea? Neither of us actually originated it!

This seems important, at least to me!

That’s an interesting aspect of pairing. Often new ideas come from the interactions, not from either individual. Neither of us would likely have thought of this but somehow the buzzing sound of his words caused a different idea in my head. Most interesting and important example of collaboration. Somehow our combined ants come up with this idea, and I think it’s a good one.

We decide to defer the hash implementation to another day, as we are running out of time. Tozier wants to test whether this code works with the other passwords. I am not convinced because I’d only turn this loose on my real site when I’m even more confident than I am now. And I’m very confident. I’m also very experienced, and justly paranoid.

Summing up

We began by reviewing our status vis-a-vis stories. Then we looked at the code, and made preparations to support the stories. We did this by improving the code. In this case, there wasn’t a tempting easy way to hammer in the needed changes but even when there is, I believe it’s generally best to improve the code rather than bash on it. Part of the big lesson from all these articles is that keeping the code clean keeps progress steady. At least I hope that’s part of the lesson.

We let ourselves be distracted, right at the beginning, by a little change we needed to make. The code we had worked, but was not strictly correct without ensure. We had coded around that problem but best to fix our Execute Around Method to work correctly.

Then we ticked rather smoothly toward setting up parameters for our object, and we made a couple of lovely discoveries along the way. We had an idea come out of our collaboration, not from either of our heads, and we noticed it happening.

And we broke a few of the “strict” rules of how we’re “supposed” to work with TDD and stories. The learning here is that while those rules are good ones, they’re “more guidelines than actual rules”, so that as we progress we can and should mindfully try coloring outside the lines and see what happens. When it works, we learn a bit about where the lines actually are. And when it doesn’t, we learn why those guidelines are pretty good ones.

All in all we enjoyed the day and we hope you did as well.

Thanks for tuning in and we’ll see you next time!