Today, I was working on my lawn. Spreading new soil to sow new grass. I was looking at the ground, and I was pretty sure drainage wasn’t going to be good. I decided to make a test: I got out the garden hose and let it flow. Sure enough, water was pooling instead of draining. Back to the drawing board (or at least, a couple more rakes to spread things around).

As I was working the soil, a thought hit me: I’d say nearly every profession has been doing test driven design (not development) since pretty much the dawn of time:

  • the toolsmith in the ancient tribes would hit the stone, see if it did work or not, and repeat as appropriate;
  • the chef tastes his food before letting other people eat it;
  • the plumber tests the pipes before letting the water flow in;
  • the soldier tests his equipment before setting off on the battlefield;
  • naval engineers are simulation testing their latest aircraft carrier in an effort to ensure nothing is out of place;
  • certain programmers write automated tests for their code, to ensure it works as designed.

If nearly every profession has been doing it for thousands of years, why aren’t you doing it today?

If you need to run Piston on Windows, BoxCycle wrote some information about it at: Rails Plugin Updates, SVN, and Piston 2.0.2 on Windows

It’s not that hard, but it still took me 2 hours to do it. I had a couple of false starts, and I pored over documentation for a while until I hit upon the excellent Nux library.

I won’t let you go through the same failures I had. Here’s the code:

 1 # Demonstrates how to parse a local HTML document using XOM,
 2 # TagSoup and Nux, under JRuby.
 3 #
 4 # http://www.xom.nu/
 5 # http://home.ccil.org/~cowan/XML/tagsoup/
 6 # http://acs.lbl.gov/nux/
 7 
 8 include Java
 9 mydir = File.expand_path(File.dirname(__FILE__))
10 
11 # This is how you require libraries without touching your
12 # CLASSPATH from JRuby. I put the required files in vendor/.
13 # Nux includes it's dependencies (XOM and saxon), so I didn't
14 # have any other libraries to add.
15 require File.join(mydir, "vendor", "tagsoup.jar")
16 %w(nux.jar saxon8.jar xom.jar).each do |filename|
17   require File.join(mydir, "vendor", "nux", "lib", filename)
18 end
19 
20 import "org.ccil.cowan.tagsoup.Parser"
21 import "nu.xom.Builder"
22 
23 builder = Builder.new(Parser.new)
24 
25 # XOM's Builder expects a full URL, so tell it where to find the
26 # document.
27 doc = builder.build("file://#{File.expand_path(File.join(mydir, ARGV[0]))}")
28 puts doc.toXML

Extra! Add XPath querying

Continuing from above, you can add XPath querying:

1 import "nux.xom.xquery.XQueryUtil"
2 
3 # Must use '*:p'.  '*' stands for any/default namespace.
4 results = XQueryUtil.xquery(doc, "//*:p")
5 p results.size
6 results.size.times do |index|
7   puts results.get(index).toXML
8 end 

Why am I going through these motions? Because I wanted to use my 20% for fun. Besides, I need to process large quantities of HTML as quickly as possible for a cool project I’m working on, and JRuby seems to be the fastest implementation, according to my unscientific benchmark.

But the real reason was that both Nokogiri and Hpricot wouldn’t load/run under JRuby 1.2.0.

Actually, let me rephrase that: Nokogiri did load, but crashed while requiring the library:

 1 $ jruby -w test.rb data.html 
 2 /Users/francois/Library/Java/JRuby/jruby-1.2.0/lib/ruby/gems/1.8/gems/nokogiri-1.2.3-java/lib/nokogiri/xml/node.rb:180: undefined method `next_sibling' for class `Nokogiri::XML::Node' (NameError)
 3         from /Users/francois/Library/Java/JRuby/jruby-1.2.0/lib/ruby/gems/1.8/gems/nokogiri-1.2.3-java/lib/nokogiri/xml/node.rb:31:in `require'
 4         from /Users/francois/Library/Java/JRuby/current/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
 5         from /Users/francois/Library/Java/JRuby/jruby-1.2.0/lib/ruby/gems/1.8/gems/nokogiri-1.2.3-java/lib/nokogiri/xml.rb:3
 6         from /Users/francois/Library/Java/JRuby/jruby-1.2.0/lib/ruby/gems/1.8/gems/nokogiri-1.2.3-java/lib/nokogiri/xml.rb:31:in `require'
 7         from /Users/francois/Library/Java/JRuby/current/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
 8         from /Users/francois/Library/Java/JRuby/jruby-1.2.0/lib/ruby/gems/1.8/gems/nokogiri-1.2.3-java/lib/nokogiri.rb:10
 9         from /Users/francois/Library/Java/JRuby/jruby-1.2.0/lib/ruby/gems/1.8/gems/nokogiri-1.2.3-java/lib/nokogiri.rb:36:in `require'
10         from /Users/francois/Library/Java/JRuby/current/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:36:in `require'
11         from test.rb:2
12 

I have reported this bug to the proper authorities.

Hpricot is another matter entirely. When I tried to use it earlier, I hit a roadblock because JRuby couldn’t install the native extensions. I tried again just now, and if you specify the version to be ~> 0.6.1, it works. Specify any other version, and you’re a sitting duck:

 1 $ jruby -S gem install -v '~> 0.6' hpricot
 2 Building native extensions.  This could take a while...
 3 ERROR:  Error installing hpricot:
 4         ERROR: Failed to build gem native extension.
 5 
 6 /Users/francois/Library/Java/JRuby/current/bin/jruby extconf.rb install -v ~> 0.6 hpricot
 7 
 8 
 9 Gem files will remain installed in /Users/francois/Library/Java/JRuby/jruby-1.2.0/lib/ruby/gems/1.8/gems/hpricot-0.8.1 for inspection.
10 Results logged to /Users/francois/Library/Java/JRuby/jruby-1.2.0/lib/ruby/gems/1.8/gems/hpricot-0.8.1/ext/hpricot_scan/gem_make.out
11 
12 $ jruby -S gem install -v '~> 0.6.1' hpricot
13 Successfully installed hpricot-0.6.164-java
14 1 gem installed
15 Installing ri documentation for hpricot-0.6.164-java...

When I’m testing admin controllers, I often have tests that follow this form:

test/functional/admin/orders_controller_test.rb
 1 class OrdersControllerTest < ActionController::TestCase
 2   logged_in_as :active_user do
 3     context "on GET to :index" do
 4       setup do
 5         get :index
 6       end
 7 
 8       should_deny_access
 9     end
10   end
11 
12   not_logged_in do
13     context "on GET to :index" do
14       setup do
15         get :index
16       end
17 
18       should_deny_access
19     end
20   end
21 end

Well, this is all Ruby, right? And Ruby has wonderful blocks, and blocks can be passed around…

test/functional/admin/orders_controller_test.rb
 1 class OrdersControllerTest < ActionController::TestCase
 2   deny_access_tests = lambda do
 3     context "on GET to :index" do
 4       setup do
 5         get :index
 6       end
 7 
 8       should_deny_access
 9     end
10   end
11 
12   logged_in_as :active_user, &deny_access_tests
13   not_logged_in, &deny_access_tests
14 end

This is valid for any block of code that you want to test again and again:

test/functional/admin/orders_controller_test.rb
 1 class OrdersControllerTest < ActionController::TestCase
 2   successful_index_render = lambda do
 3     should_respond_with :success
 4     should_render_template "new"
 5     should_assign_to :orders
 6   end
 7 
 8   logged_in_as :admin do
 9     context "", &successful_index_render
10   end
11 
12   logged_in_as :sub_admin do
13     context "", &successful_index_render
14   end
15 end

Alternatively, and it might be easier in the end, you could use methods:

test/functional/admin/orders_controller_test.rb
 1 class OrdersControllerTest < ActionController::TestCase
 2   def self.should_render_successful_index_response
 3     should_respond_with :success
 4     should_render_template "new"
 5     should_assign_to :orders
 6   end
 7     
 8   logged_in_as :admin do
 9     should_render_successful_index_response
10   end
11 
12   logged_in_as :sub_admin do
13     should_render_successful_index_response
14   end
15 end

Note thought that you must define your methods at the top of your test case. Remember that Ruby executes a class definition, so when you suddenly call should_render_successful_index_response, the method definition has to be available, or else Ruby will complain with a NoMethodError.

Ain’t Ruby sweet?

Image a Ruby where if/then/else isn’t available:

1 class Account
2   # This method returns the balance in words, negative or positive, ready for display in the UI
3   def balance_in_words
4     # Err...  How do I do that?
5   end
6 end

If you were a strict Object-Oriented person, you’d do it this way:

 1 class PositiveBalanceAccount
 2   def balance_in_words
 3     "Positive"
 4   end
 5 end
 6 
 7 class NegativeBalanceAccount
 8   def balance_in_words
 9     "Negative"
10   end
11 end

But then, your objects would have to change class whily-nilly. Pretty darn hard. But multi-method dispatching gives us a nice solution:

 1 class Account
 2   def initialize(balance)
 3     @balance = balance
 4   end
 5 
 6   defmulti :balance_in_words,
 7     lambda { @balance < 0 } => "Negative",
 8     lambda { @balance > 0 } => "Positive"
 9 end
10 
11 Account.new(15).balance_in_words # => "Positive"
12 Account.new(-5).balance_in_words # => "Negative"

For the observant amongst you, you might have noticed I forgot the nil balance case. This is really a programming error, so it should be treated as such:

1 Account.new(0).balance_in_words
2 lib/defmulti.rb:46:in `balance_in_words': #<Account:0x197222c @balance=0> received balance_in_words but did not have a guard clause that matched and no else clause. (Defmulti::MissingGuardClause)
3         from test.rb:16
4 

For the even more observant, yes, there exists an implementation that does exactly what I have described above. It’s called defmulti, it’s a gem, and it’s on GitHub: http://github.com/francois/defmulti.

What is this useful for?

This library is a thought experiment. When you lose the familiar tools, what can you do? Of course, this library works atop the existing Ruby implementation and to be truly useful, syntax would have to be provided to make this much less verbose. It’s interesting nonetheless to see what can be done without the syntax extensions.

Anyway, what would you use this for? Multi-method dispatching is a tool that helps writing code without conditionals. The conditionals are specified outside the block of code that executes. The examples above are pretty thin, but looking at Java.next #3: Dispatch, I can provide another solution to his Ruby example:

 1 defmulti :letter_grade,
 2   lambda {|grade| (90..100).include?(grade) || grade == "A"} => "A",
 3   lambda {|grade| (80...90).include?(grade) || grade == "B"} => "B",
 4   lambda {|grade| (70...80).include?(grade) || grade == "C"} => "C",
 5   lambda {|grade| (60...70).include?(grade) || grade == "D"} => "D",
 6   lambda {|grade| ( 0...60).include?(grade) || grade == "F"} => "F"
 7 
 8 letter_grade 60  # => "D"
 9 letter_grade "A" # => "A"
10 letter_grade nil # => Defmulti::MissingGuardClause

Again, I’m struck by the clunky syntax, but if we ignore that for a second, could this be even better than Stuart Halloway’s example? Ruby’s case statement is very, very powerful and very easy to use. Is this useful? Not at the moment. But it’s a thought experiment I thought I’d throw out there.

A while ago, I was wondering how rename tracking in Git worked. I was told that renames didn’t really exist in Git, as Git tracked content, not files themselves. Fair enough.

But, I just stumbled upon something:

 1 $ gdc vendor/plugins/acts_as_money/LICENSE 
 2 diff --git a/vendor/plugins/acts_as_money/LICENSE b/vendor/plugins/acts_as_money/LICENSE
 3 index e69de29..a273c73 100644
 4 --- a/vendor/plugins/acts_as_money/LICENSE
 5 +++ b/vendor/plugins/acts_as_money/LICENSE
 6 @@ -0,0 +1,4 @@
 7 +one:
 8 +  user: active
 9 +  name: name
10 +  description: description

Tell me, does that look right? I’ll manually fix that file, but that just decreased my confidence level in Git.

The original LICENSE file was empty, and there apparently was another file (a fixture file) that was empty too, and the latter saw some content added.

Why did this happen? LICENSE was updated as part of a merge—from a branch in which the LICENSE file doesn’t exist.

Back in 2006, I wrote Returning CSV data to the browser. The method I was using back then is way obsolete. Let’s use the correct way to do it. If you want to follow along, just browse over to returning-csv-data-to-the-browser-revisited and read on.

First of all, the correct way to return non HTML data to the browser is to use respond_to. Let’s do so here:

app/controllers/reports/timelines_controller.rb
 1 class Reports::TimelinesController < ApplicationController
 2   def show
 3     @timelines = Timeline.all
 4     respond_to do |format|
 5       format.csv do
 6         response.headers["Content-Type"]        = "text/csv; charset=UTF-8; header=present"
 7         response.headers["Content-Disposition"] = "attachment; filename=timeline-report.csv"
 8       end
 9     end
10   end
11 end

The rest is pretty easy. Generating the data is easy enough:

app/models/timeline.rb
 1 require "fastercsv"
 2 
 3 class Timeline < ActiveRecord::Base
 4   default_scope :order => "started_at ASC"
 5 
 6   def to_csv
 7     FasterCSV.generate_line([
 8           started_at.to_s(:db),
 9           ended_at.to_s(:db),
10           project_id]).chomp
11   end
12 end

And rendering the view? Look how easy this gets:

app/views/reports/timelines/show.csv.erb
1 Started At,Ended At,Project ID
2 <%= render :partial => @timelines %>

Notice the file’s name? show.csv.erb? The csv in the filename is what connects the respond_to call with the view.

And the final piece, the timeline partial:

app/views/reports/timelines/_timeline.csv.erb
1 <%= timeline.to_csv %>
2 

Now, on to the next obsolete article in the bunch…

Back in October 2005, I wrote an article entitled Building the SQL WHERE Clause Dynamically. This article is obsolete and I’m replacing it with this one instead.

What do you do with a FORM that has multiple conditions? Something like this:

The way I would do it today is using named scopes:

app/models/person.rb
 1 class Person < ActiveRecord::Base
 2   is_taggable
 3 
 4   named_scope :in_region,
 5     lambda {|region| {:conditions => {:region => region}}}
 6   named_scope :with_salary,
 7     lambda {|lower, upper| {:conditions =>
 8       {:salary => (lower .. upper)}}}
 9 
10   # Elided, but something similar to the above
11   named_scope :tagged_with,
12     lambda {|tags| {:conditions =>
13       ...}}
14 end

Then you need your controller to translate between what the view provides and what the model expects:

app/controllers/searches_controller.rb
 1 class SearchesController < ApplicationController
 2   def show
 3     root = Person
 4     root = root.in_region(params[:region]) unless params[:region] =~ /all/i
 5     unless params[:salary] =~ /all/i
 6       params[:salary].gsub!("K", "000")
 7       lower, upper = params[:salary].split("-")
 8       root = root.with_salary(lower, upper)
 9     end
10     root = root.tagged_with(Tag.parse(params[:tags])) unless params[:tags].blank?
11     @people = root.paginate(:page => params[:page])
12   end
13 end

For simpler forms, the approach above would be sufficient. For more complex forms, I suggest you look at plugins that aim to provide more structure around your SQL clauses, such as:

I also suggest looking at Searching and Queries Plugins – Agile Web Development to find other plugins.

Back in October 2005, I wrote an article entitled Actions with Dashes. This article is perfectly obsolete and uses a trick instead of the router as it was intended.

Let’s say you want an action where the URL is http://myproject.com/contact-us. The best way to do that currently is to use Rails’ router:

config/routes.rb
1 map.contact_us "/contact-us", :controller => "pages", :action => "show", :page => "contact_us"

app/controllers/pages_controller.rb
1 class PagesController < ActionController
2   def show
3     render :action => params[:page]
4   end
5 end

The PagesController above is also good for any other static pages you want to serve on your site.

A couple of days ago, @jfcouture tweeted:

@cmercier People with stuff older than 6 months in the Rails world on their blog should do the same!

@jfcouture via Twitter

This was in response to Carl’s rant that obsolete Merb tutorials should be removed / deprecated.

I decided to follow their advice. I’m revisiting all my old posts and saying they are either obsolete, or providing links to replacements. Expect to see at least a post a day for the next couple of weeks as I revisit my old posts. This is interesting! I had forgotten about a lot of things I had written about.