Something completely different

Posted: July 3rd, 2009 | Author: Jamal | Filed under: Uncategorized | Tags: | No Comments »

Totally off topic, but I laughed when I saw this.


Getting started with ISpec and Ioke – part 4

Posted: June 23rd, 2009 | Author: Jamal | Filed under: Ioke | Tags: | No Comments »

Part 4

This is part 4 of the increasingly mis-named Getting started with Ispec and Ioke trilogy. Last time we discussed the let keyword and I started working on the template object which represented the template to be parsed into a document.

Previously I used ISpec to assert that we could evaluate on cells on Bag. I wanted to start off this time by proving to myself that I could just as easily use activatable cells on Bag as well.

    it("should evaluate method on bag",
      bag = Bag mimic
      bag bread = method("butter")
      evaluator = Evaluator mimic(bag)
      evaluator evaluate(bread) should == "butter"
    )

As you can see, this time instead of assigning a string to a cell on the property bag an evaluating it, I instead assigned a method which returned a string to a cell and evaluated it. Sure enough it worked without any changes to the code. This creates some interesting possibilities, though I haven’t thought through all of the ramifications at the moment.

Sure enough it worked.

Next I wanted to push the envelope on Evaluator a little bit by defining the method which will do the work of parsing the template and injecting the values from the property bag into it.

describe("parsing",
    it("should return the template when the bag is nil",
      evaluator = Evaluator mimic(nil)
      template = Template mimic
      template text = "An extra special template"
 
      document = evaluator combine(template)
      document should == "An extra special template"
    )
  )

This is really a small step, and as I get more familiar with TDD and BDD, these seem to the the steps that I’m most comforable with lining up and knocking down when implementing new features. The other part of this is, doing the simplest thing that could possibly work to pass the spec / test. In this case, this is the simplest thing.

Evaluator combine = method (
  "Generates a document from a template and bag",
 
  template,
  template text
)

From here, I think that the next step is going involve using Ioke’s implementation of Regexp, which I will need to read up on. So I think I will cut this short for today and wait for next time for that topic.

The Code

The code for this example is available on github tagged as start_ispec_4


Getting a project off the ground with TDD and Cucumber

Posted: June 21st, 2009 | Author: Jamal | Filed under: Ruby, Testing | Tags: , , | No Comments »

RSimpy

I am currently wrapping up an initial release of a new gem that wraps the Simpy.com API. I like social bookmarking and used delicious for years before switching to Simpy as my primary service. Simpy is not the biggest online bookmarking service, but I’ve decided to use it for reasons I will outline in another post. In any case I’ve put together a gem that wraps the API with the help of Httparty. It actually turned out much simpler than I expected it to be, httparty did all of the heavy lifting, but again that is for another post.

So, I have a functioning gem for Simpy, now I want to make something useful with it. The reason I made the gem was to simplify my bookmarking which is overall a mess on multiple workstations in multiple locations. Since I’ve recently been getting familia with cron, I’ve decided to make a script to store urls locally and use cron to run a script that pushes a file of pages to Simpy.

As I touched on briefly, I’ve been unhappy with all my previous bookmarking solutions thus far. This time I want to make it, dead simple. I don’t want to have to enter any additional information if I don’t want to, just a url and have it go on it’s merry way. However to post the link, I will need to at least have the page title. Pulling a page title from a url is really outside of the scope of RSimpy. Ruby makes it fairly simple to do, but I don’t want to isolate the functionality to a script. Ruminate to the rescue, but what is Ruminate?

Ruminate

Well the answer to that question is that I’m not really sure. A few months back, I made a gem with jeweler called ruminate with the description “Extracts statistics from html documents”. Actually, I’m not really sure what I originally had in mind when I setup this gem, but it will be useful now since I want to extract at least the page title. Once I get the ability to extract a page title, I can make a script that is simply glue code to put Ruminate together with RSimpy and voila!

Bootstrapping the project

I have a vague idea of something that I want Ruminate to do, that is I want to be able to query a url to return the title of the page. It’s obviously not the entire purpose of the gem, but I’m sure that will be lots of other bits that will come together in time.

The question is, How do I kickstart this project with TDD to get it off the ground?

Programming by Intention

Programming by intention is the practice of writing code, while pretending that any methods or classes you might reference already exist and operate in the way that you reference them. It’s a great way of uncovering a simple intuative API that you might otherwise would have missed.

I’ve seen it described in two books, first in Everyday Scripting with Ruby, from The Pragmatic Bookshelf. Secondly, it’s used in an excellent book I’m currently reading called Test Driven from Manning.

There are a number of sources stating that TDD is about design first and automated testing is a nice by-product. In my limited experience with TDD, programming by intention is the crux of this design, and by extension the primary purpose of TDD.

Cucumber

One of the things that I really like about cucumber is that you can plainly state your intent and important bits of your feature focused domain in plain English and match that up to executable code. To describe the ability of Ruminate to grab the title of a url, I made the following test:

?View Code CUCUMBER
Feature: Get Page Title
  In to get the page title
  A user of ruminate
  Will request a page title
 
  Scenario: Requesting the title of a page from the page
    Given the url "http://www.google.com"
    When I execute the request
    Then the "title" should be "Google"

I let cucumber auto-generate the regular expression blocks and came up with the following steps:

 
Given /^the url "([^\"]*)"$/ do |url|
  @url = url
end
 
When /^I execute the request$/ do
  query = "Select title from #{@url};"
  @result = chew query
end
 
Then /^the "([^\"]*)" should be "([^\"]*)"$/ do |msg, value|
  assert_equal value, @result
end

As you can see I’ve decided to try out a sql like syntax for the tool. I thought it sounded interesting, we’ll see if I keep it. In any case, I now need to make this test pass with the help of a mixin, that I’m including in features/support/env.rb

# features/support/env.rb
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
require 'ruminate'
include Ruminate
 
require 'test/unit/assertions'
 
World(Test::Unit::Assertions)
# lib/ruminate.rb
require 'query_parser'
require 'engine'
 
module Ruminate
  def chew query
    parser = Ruminate::QueryParser.new
    query_object = parser.parse query
    engine = Ruminate::Engine.new
 
    engine.execute query_object
  end
end

Here I’ve used the mixin to split the method into to operations and encapsulated each of these into a class. Programming by intention has now generated a mixin and two classes that have a clean separation of responsibilities. Here is the simplest thing that could possibly work to make the feature pass.

# lib/query_parser.rb
module Ruminate
  class QueryParser
    def parse query
      nil
    end
  end
end
# lib/engine.rb
module Ruminate
  class Engine
    def execute query
      "Google"
    end
  end
end

Obviously this isn’t the final code, but it gives us the minimal implementation to pass the feature. Now I can turn to Test::Unit and shoulda to flesh out tests and implementations to make it work in a more useful manner.

What I really wanted to show here is how to get a TDD project started. I know that I’ve struggled with this in the past. What to test first can be a big decision, one that can create a type of code writers block. When this moment happened where everything just came together and I was able to generate a mixin and two orthagonal classes with a simple feature, I wanted to write it up.

The Code

The full code for this example can be found on github with the tag simplest-thing-that-could-possibly-work


Chores: A test driven website – Part 10

Posted: June 8th, 2009 | Author: Jamal | Filed under: Chores | Tags: , | No Comments »

Where was I?

By the time I finished up post nine and started working on post 10, I was completely lost. It was entirely my fault and it happens to the best of us, and me too. Coming back to the Chores app after time had passed, and looking around, I thought two things:

  • How did this get to be such a mess?
  • Where was I?

A number of factors had come into play that led to this point. The first of which was lack of discipline. As came across features I thought were interesting or necessary, I abandoned the test driven mantra of red, green, refactor. This ruined my focus, splintered my development efforts and generated a fair share of untested code.

Another issue I ran into is that over the time between my first posts and now, a number of new versions of libraries were released and Rails itself moved from version 2.2.2 to 2.3.2. Subtle variations in the APIs for these gems, generated a few errors and left me scratching my head more than once. I would like to say that change is good, it’s great that the Ruby community is so active in improving it’s products. However, I needed to be more proactive in managing the changes.

Time is another factor that worked it’s entropy in Chores. You hear people talking about the poor developer left to maintain sloppy code months or years after it’s release, and that they may be you. Well it was me, and I was feeling the hurt.

Not all is lost

Thus far, I’ve painted a pretty dire picture of the state of the code. I’ve probably left you thinking, “but this is Chores: A test driven website! How could this happen?” Well it does happen, but you are right. On the bright side, it is a test driven website. If it wasn’t I would probably be tempted to toss it out and start over. But, because it’s test driven and in source control, I’m fairly confident that I can get the codebase up to date and functional in a reasonable amount of time. This in addition to a few practices will have me back in business in no time

While these are good practices to follow in the future, what can I do to get this codebase back under control?

  • Git back to known state – Since I’ve done some unknown piecemeal development on chores I will use my post_9 tag.
  • Make sure existing tests pass – This is where our tests are worth their weight. We know what code is working, and what that code does.
  • Evaluate holes – Find the weak points in the tests and make a plan to fix them.

Git Happy

Note: You can find post_9 here, though if you happen to be following along, you would want to merge it into your own source.

My first step is to rewind to the tag I made for my last post. Right away I realise that I now have two source repositories, one on my shared host and one on github. One is really enough so part of the process will be to move to only github.

rubyyot@atlas:~/working/chores$ git status                                          
# On branch master                                                                  
# Your branch is ahead of 'origin/master' by 4 commits.                             
#                                                                                   
nothing to commit (working directory clean)                                         
rubyyot@atlas:~/working/chores$ git remote                                          
github                                                                              
origin                                                                              
rubyyot@atlas:~/working/chores$ git remote rm origin
rubyyot@atlas:~/working/chores$ git remote          
github                                              
rubyyot@atlas:~/working/chores$ git remote add origin   git://github.com/rubyyot/chores.git

Above, I listed my remote repositories. I see that I have two, so I remove one and add another. A mistake I made here is that I set my remote to my public clone url. I should have, since I will be pushing to it, used my clone url, git@github.com:rubyyot/chores.git.

rubyyot@atlas:~/working/chores$ git status                                                 
rubyyot@atlas:~/working/chores$ git reset --hard post_9
HEAD is now at b0eecae added auto formatting to Identity.find_or_create_if_valid

Here I have done a hard (destructive) reset back to the tag post_9. I could have avoided doing such a dangerous procedure if I had been working on a branch.

rubyyot@atlas:~/working/chores$ git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       db/production.sqlite3
nothing added to commit but untracked files present (use "git add" to track)    
rubyyot@atlas:~/working/chores$ vi .gitignore      #added db/*.sqlite3 to .gitignore
rubyyot@atlas:~/working/chores$ git status
# On branch master                        
# Changed but not updated:                
#   (use "git add <file>..." to update what will be committed)
#                                                             
#       modified:   .gitignore                                
#                                                             
no changes added to commit (use "git add" and/or "git commit -a")

Here I noticed that git sees my “production” sqlite3 database is not in sync with the index. I really don’t want or need to have a pseudo-production sqlite3 database in source control so I updated my .gitignore file to ignore it.

rubyyot@atlas:~/working/chores$ rake test 
(in /home/rubyyot/working/chores)         
/usr/bin/ruby1.8 -I"/home/rubyyot/working/chores/lib" -I"/home/rubyyot/working/chores/test" "/home/rubyyot/.gem/ruby/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader.rb" "test/unit/identity_test.rb" "test/unit/chore_test.rb" "test/unit/child_test.rb"                                                                                   
Loaded suite /home/rubyyot/.gem/ruby/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader                            
Started                                                                                                       
.............................                                                                                 
Finished in 0.347031 seconds.                                                                                 
 
29 tests, 43 assertions, 0 failures, 0 errors
/usr/bin/ruby1.8 -I"/home/rubyyot/working/chores/lib" -I"/home/rubyyot/working/chores/test" "/home/rubyyot/.gem/ruby/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader.rb" "test/functional/children_controller_test.rb" "test/functional/chores_controller_test.rb" "test/functional/home_controller_test.rb"                                
Loaded suite /home/rubyyot/.gem/ruby/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader                            
Started                                                                                                       
..........                                                                                                    
Finished in 0.201701 seconds.                                                                                 
 
10 tests, 17 assertions, 0 failures, 0 errors
/usr/bin/ruby1.8 -I"/home/rubyyot/working/chores/lib" -I"/home/rubyyot/working/chores/test" "/home/rubyyot/.gem/ruby/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader.rb"                                                      
rubyyot@atlas:~/working/chores$ rake features                                                                 
(in /home/rubyyot/working/chores)                                                                             
/usr/bin/ruby1.8 -I "/usr/lib/ruby/gems/1.8/gems/cucumber-0.3.11/lib:lib" "/usr/lib/ruby/gems/1.8/gems/cucumber-0.3.11/bin/cucumber" --format pretty features/define_children.feature features/define_chores.feature        
Story:  Define child                                                                                          
As a parent                                                                                                   
I want to define my child(ren)                                                                                
So that I can assign them chores                                                                              
 
  Scenario: Defining a child                                        # features/define_children.feature:6
    Given I am logged in as "test.example.com"                      # features/step_definitions/chores_steps.rb:9                                                                                                           
    And I am on the homepage                                        # features/step_definitions/webrat_steps.rb:6                                                                                                           
    When I follow "Add Child"                                       # features/step_definitions/webrat_steps.rb:18                                                                                                          
    And I fill in "child[nickname]" with "Bobby"                    # features/step_definitions/webrat_steps.rb:22                                                                                                          
    And I fill in "child[open_identifier]" with "bobby.example.com" # features/step_definitions/webrat_steps.rb:22                                                                                                          
    And I press "Add"                                               # features/step_definitions/webrat_steps.rb:14                                                                                                          
    Then I should see "Child added."                                # features/step_definitions/webrat_steps.rb:93                                                                                                          
    And I should see "Bobby"                                        # features/step_definitions/webrat_steps.rb:93                                                                                                          
 
  Scenario: Defining a second child                                 # features/define_children.feature:16
    Given I am logged in as "test.example.com"                      # features/step_definitions/chores_steps.rb:9                                                                                                           
    And I am on the homepage                                        # features/step_definitions/webrat_steps.rb:6                                                                                                           
    When I follow "Add Child"                                       # features/step_definitions/webrat_steps.rb:18                                                                                                          
    And I fill in "child[nickname]" with "Bobby"                    # features/step_definitions/webrat_steps.rb:22                                                                                                          
    And I fill in "child[open_identifier]" with "bobby.example.com" # features/step_definitions/webrat_steps.rb:22                                                                                                          
    And I press "Add"                                               # features/step_definitions/webrat_steps.rb:14                                                                                                          
    And I follow "Add another Child"                                # features/step_definitions/webrat_steps.rb:18                                                                                                          
    And I fill in "child[nickname]" with "Suzy"                     # features/step_definitions/webrat_steps.rb:22                                                                                                          
    And I fill in "child[open_identifier]" with "suzy.example.com"  # features/step_definitions/webrat_steps.rb:22                                                                                                          
    And I press "Add"                                               # features/step_definitions/webrat_steps.rb:14                                                                                                          
    Then I should see "Child added."                                # features/step_definitions/webrat_steps.rb:93                                                                                                          
    And I should see "Bobby"                                        # features/step_definitions/webrat_steps.rb:93                                                                                                          
    And I should see "Suzy"                                         # features/step_definitions/webrat_steps.rb:93                                                                                                          
 
Story:  Define chores
As a parent          
I want to define chores
So that I can assign them to my children
 
  Scenario: Creating a chore                      # features/define_chores.feature:6
    Given I am logged in as "test.example.com"    # features/step_definitions/chores_steps.rb:9
    And I am on the homepage                      # features/step_definitions/webrat_steps.rb:6
    When I follow "Add Chore"                     # features/step_definitions/webrat_steps.rb:18
    And I fill in the chore with "My first chore" # features/step_definitions/chores_steps.rb:5 
    And I press "Add"                             # features/step_definitions/webrat_steps.rb:14
    Then I should see "Chore added."              # features/step_definitions/webrat_steps.rb:93
    And I should see "My first chore"             # features/step_definitions/webrat_steps.rb:93
 
3 scenarios (3 passed)
28 steps (28 passed)  
0m0.266s

Now I’ve run my tests and features and am pleasantly surprised to see that they are all passing. I didn’t expect this, but it’s a pleasant surprise. Still there could be some hidden surprises lurking in the shadows.

rubyyot@atlas:~/working/chores$ git status
# On branch master                        
# Changed but not updated:                
#   (use "git add <file>..." to update what will be committed)
#                                                             
#       modified:   .gitignore                                
#       modified:   db/test.sqlite3                           
#                                                             
no changes added to commit (use "git add" and/or "git commit -a")   
rubyyot@atlas:~/working/chores$ git rm -f db/test.sqlite3     
rm 'db/test.sqlite3'                                          
rubyyot@atlas:~/working/chores$ git status                    
# On branch master                                            
# Changes to be committed:                                    
#   (use "git reset HEAD <file>..." to unstage)               
#                                                             
#       deleted:    db/test.sqlite3                           
#                                                             
# Changed but not updated:                                    
#   (use "git add <file>..." to update what will be committed)
#                                                             
#       modified:   .gitignore                                
#                                                             
rubyyot@atlas:~/working/chores$ git add .gitignore
rubyyot@atlas:~/working/chores$ git status        
# On branch master                                
# Changes to be committed:                        
#   (use "git reset HEAD <file>..." to unstage)   
#                                                 
#       modified:   .gitignore                    
#       deleted:    db/test.sqlite3               
#

After running my tests my sqlite3 test db shows as modified. Since my modification to .gitignore should be excluding all sqlite3 databases, it must already be in the git index. So I removed it and added the updated .gitignore to the index. I’m all ready for a commit.

rubyyot@atlas:~/working/chores$ git commit -m "resetting to post_9"
Created commit 79d6f46: resetting to post_9                        
 2 files changed, 1 insertions(+), 0 deletions(-)                  
 delete mode 100644 db/test.sqlite3                                
rubyyot@atlas:~/working/chores$ git push github
git push github                                
Enter passphrase for key '/home/rubyyot/.ssh/id_rsa': 
To git@github.com:rubyyot/chores.git                  
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'git@github.com:rubyyot/chores.git'

I make my commit and try to push to github. The attempt is rejected. Looks like I need to merge into it.

rubyyot@atlas:~/working/chores$ git pull github master
Enter passphrase for key '/home/rubyyot/.ssh/id_rsa': 
From git@github.com:rubyyot/chores                    
 * branch            master     -> FETCH_HEAD         
Merge made by recursive.                              
 README |    6 ++++++                                 
 1 files changed, 6 insertions(+), 0 deletions(-)    
rubyyot@atlas:~/working/chores$ git merge ac60baa287c7ee73851d1b9895c7e6c826abddac
Already up-to-date.

I pull from github which appears to auto-merge. My manual attempt to merge tells me that I’m up to date.

rubyyot@atlas:~/working/chores$ vi README   #made a small change to README
rubyyot@atlas:~/working/chores$ git status
# On branch master                        
# Your branch is ahead of 'origin/master' by 3 commits.
#                                                      
# Changed but not updated:                             
#   (use "git add <file>..." to update what will be committed)
#                                                             
#       modified:   README
#
no changes added to commit (use "git add" and/or "git commit -a")
rubyyot@atlas:~/working/chores$ git add .
rubyyot@atlas:~/working/chores$ git commit -m "small change to try commit to github"
Created commit 9991b45: small change to try commit to github
 1 files changed, 1 insertions(+), 1 deletions(-)
rubyyot@atlas:~/working/chores$ git push github
Enter passphrase for key '/home/rubyyot/.ssh/id_rsa':
Counting objects: 15, done.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 955 bytes, done.
Total 9 (delta 5), reused 0 (delta 0)
To git@github.com:rubyyot/chores.git
   ac60baa..9991b45  master -> master
rubyyot@atlas:~/working/chores$ git status
# On branch master                                                                         
nothing to commit (working directory clean)

I made a small change and attempted to commit to github. It worked, yay!

rubyyot@atlas:~/working/chores$ vi README  #another small change to README     
rubyyot@atlas:~/working/chores$ git status                                                 
# On branch master                                                                         
# Changed but not updated:                                                                 
#   (use "git add <file>..." to update what will be committed)                             
#                                                                                          
#       modified:   README                                                                 
#                                                                                          
no changes added to commit (use "git add" and/or "git commit -a")                          
rubyyot@atlas:~/working/chores$ git add .                                                  
rubyyot@atlas:~/working/chores$ git commit -m "another small commit to test new remote to github"
Created commit a5b8a72: another small commit to test new remote to github                        
 1 files changed, 1 insertions(+), 1 deletions(-)                                                
rubyyot@atlas:~/working/chores$ git push origin                                                  
fatal: protocol error: expected sha/ref, got '
*********'
 
You can't push to git://github.com/user/repo.git
Use git@github.com:user/repo.git
 
*********'

You will recall that earlier I removed my remote to my shared host and replaced it with github. Here I tried to push another small commit to it and it failed. I used my public clone url rather than my private ssh protected one, as I mentioned earlier. Time to fix that.

rubyyot@atlas:~/working/chores$ git remote rm origin
rubyyot@atlas:~/working/chores$ git remote add origin git@github.com:rubyyot/chores.git
rubyyot@atlas:~/working/chores$ git status
# On branch master
nothing to commit (working directory clean)
rubyyot@atlas:~/working/chores$ git push origin
Enter passphrase for key '/home/rubyyot/.ssh/id_rsa':
Counting objects: 5, done.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 330 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
To git@github.com:rubyyot/chores.git
   9991b45..a5b8a72  master -> master
rubyyot@atlas:~/working/chores$ git gc
Counting objects: 352, done.
Compressing objects: 100% (244/244), done.
Writing objects: 100% (352/352), done.
Total 352 (delta 105), reused 275 (delta 71)
rubyyot@atlas:~/working/chores$

All fixed up and I’m back in sync. I am still new to git and am only familiar with the handful of commands I use on a daily basis. There was probably a better or easier way to do what I did. If so, feel free to comment. If you are new to git too here are a couple resources.

How do I?

Below I included a small experiment I did where I cloned the chores source to a new local repository, checked out the post_9 tag to a new branch, modified origin to point to another repository, and checked the branch into master. Hopefully it will give you something to work with if you want to do anything similar. Alternatively, you could fork my github source into your own github repo and have fun.

 
rubyyot@atlas:~/working$ git clone   git://github.com/rubyyot/chores.git chores-github
Initialized empty Git repository in /home/rubyyot/working/chores-github/.git/         
remote: Counting objects: 352, done.
remote: Compressing objects: 100% (227/227), done.
remote: Total 352 (delta 105), reused 320 (delta 88)
Receiving objects: 100% (352/352), 139.50 KiB, done.
Resolving deltas: 100% (105/105), done.             
rubyyot@atlas:~/working$ cd chores-github/              
rubyyot@atlas:~/working/chores-github$ git branch post_9_branch post_9
rubyyot@atlas:~/working/chores-github$ git checkout post_9_branch
Switched to branch "post_9_branch"                               
rubyyot@atlas:~/working/chores-github$                           
rubyyot@atlas:~/working/chores-github$ git remote rm origin
rubyyot@atlas:~/working/chores-github$ git remote add origin ssh://USERNAME@example.com/path/to/repo
rubyyot@atlas:~/working/chores-github$ git pull origin master
USERNAME@example.coms password:
From ssh://USERNAME@example.com/path/to/repo
 * branch            master     -> FETCH_HEAD
Already up-to-date.
rubyyot@atlas:~/working/chores-github$ git status
# On branch post_9_branch
nothing to commit (working directory clean)
rubyyot@atlas:~/working/chores-github$ git checkout master
Switched to branch "master"
rubyyot@atlas:~/working/chores-github$ git merge post_9_branch
Already up-to-date.
rubyyot@atlas:~/working/chores-github$ git status
# On branch master
nothing to commit (working directory clean)
rubyyot@atlas:~/working/chores-github$

Conclusion

In all it seems that things were not as bad as I had originally thought. Still it reinforces the need for discipline in doing TDD and coding in general. In the next post I will look over the health of the app and (hopefully) take appropriate steps to correct any problems.


Getting started with ISpec and Ioke – part 3

Posted: June 4th, 2009 | Author: Jamal | Filed under: Ioke | Tags: | No Comments »

Ruby, not that there’s anything wrong with that

A little while ago, I issued my Summer of Learning Challenge. As a suggestion, Ioke would be a great thing to learn for those that would like to learn something this summer, but aren’t sure what to learn. My project for the challenge is Ruby based, though I have a goal for the year to learn Ioke. As you can see from my Calendar About Nothing, which currently has a 15 day string of commits, I’ve been busily plugging away at both.

A few days back, Sam Aaron stopped by and commented on part 2 of this series, pointing out an error with my code.

Oops

Well, there were two errors there. First my spec stated that Bag should evaluate nil as an empty string, but code specified that it should be nil. Secondly, I had a problem with the way I had constructed my message chain so that my spec was evaluating nil should == nil. Of course this will always evaluate to true. I patched it up with the help of some advice in the comments. In the future I’ll need to remember to be more explicit in my intent by remembering to use parens when passing parameters to methods so that they aren’t interpreted as part of the message chain.

My initial solution, which I updated to the post, was to monkeypatch bag so that the cell nil returned an empty string. This solution didn’t really set well with me, but it passed the spec so I stuck with it. After some rest, I remembered reading about the keyword let.

The joy of let

I wasn’t familiar with the let method from the other languages I use, which are primarily Ruby and C#. But a little research showed that it provided an alternate solution to the specification. Opening up the specification for let, you will see that it accepts zero or more comma separated name value pairs and a block of code. It will then create a new temporary scope where the name is assigned the value and the code block is evaluated. At the end of the execution of the code block, the scope goes away and everything returns to normal.

Evaluator = Origin mimic
 
Evaluator initialize = method (
  "Pass the property bag into the evaluator, this will be used for future evaluations.", 
 
  bag, 
  @bag = bag
)
 
Evaluator evaluate = macro (
  "Takes the supplied message and evaluates it on the property bag. If nil is passed, it will return an empty string",
 
  msg = call arguments first
  let(bag nil, "",
    msg evaluateOn(bag)
  )
)

This is an interesting alternative. It provides a way to temporarily modify an object while ensuring that your modifications don’t touch anything else once you are done.

Variations on a theme

One thing that I found particularly interesting is that all of the following variations seem to work

  let(@bag nil, "",
    msg evaluateOn(bag)
  )
  let(bag nil, "",
    msg evaluateOn(@bag)
  )
  let(Bag nil, "",
    msg evaluateOn(bag)
  )

I assume that the first two versions work because the sigil version of bag (@bag) simply points to the instance stored in the cell bag on the evaluator object. In other words, when using Evaluator as ground, from inside the object, bag and @bag have the same meaning. I’ll need to look into this to see if what I suspect is true.

I think that the third variation works because bag mimics Bag and so changes to Bag cascade to bag. If it is true that modifications to the parent object made after the mimic has been made will cascade to all mimics, then that is a very powerful and easily abused feature of Ioke. Again these are speculation on my part, and I will need to look into the truth of the matter.

Starting Templates

Once I had that figured out I started thinking of the next specification to tackle. I decided that if I’m going to be able to use Ioke, I’d need to figure out how to work with files. I feared that working with files might have been left out of the Ioke implementation as it is a very young language. As it turns out, I was dead wrong. The FileSystem object is implemented and quite easy to use.

Ioke and the FileSystem

I was quite happy to find a spec for the FileSystem so that I could get a feel for it. It turns out that there are a number of useful methods available and it appears to have support for Windows filesystems as well.

I decided to have my template object check to see if the string passed to it is a valid file. If it is, it will load it. If not, it will assume that the string passed to it is the template text. I made it this way for testing purposes more than anything, and may change it around if it causes issues. But it was simple to setup and it works.

 
Template = Origin mimic
 
Template load = method("load will load a template file.",
 
  template,
  if(FileSystem exists?(template),
    Template text = FileSystem readFully(template),
    Template text = template
  )
)

The Code

Current source snapshot is available on github tagged as start_ispec_3