Pushrod

Old dogs, new tricks

Posts Tagged ‘unit tests

Rails 2.0 gotcha: count_from_query plugin

leave a comment »

I’ve got a fairly comprehensive test suite for Autopendium :: Stuff About Old Cars, the classic car community site I run, which makes upgrades of the framework fairly stress-free.

By stress-free, however, I don’t mean trouble-free — there are going to be failing tests, and there are going to be problems. However, I’m fairly confident that if the tests pass, the update to the production server will be without problems (particularly since I’ve started using a staging server).

So it was with upgrading to 1.2.6 — I had only a couple of deprecation warnings, and some failing tests, most of which were due to some problems with my routing.

Upgrading to 2.0.2 has proved a bit trickier however, mainly because the error messages (and they are errors, rather than fails) aren’t helping me in finding the root cause of the problem, only telling me what ultimately brings the whole thing crashing down.

Running the unit tests via the console I get this horror:

Rails 2.0 unit test errors

OK. Let’s take this bit by bit. So I run the unit tests for WikipediaEntry:

Rails 2.0 unit test passes

Hmm. This smells… and the smell is called… fixtures.

Log story short, by trawling through the test logs, using ruby-debug, and getting to grips with the ActiveRecord code, I found out that the count_from_query plugin I use (which makes generating counts from complex custom finders a cinch) wasn’t playing well with the new ActiveRecord behaviour, which has changed a bit since the 1.2 branch, nor with the new faster fixtures.

[As an aside, this sort of thing is why if you’re serious about using Rails you must learn Ruby, and why it’s a good idea only to use lightweight plugins you can understand.]

The offending line in the plugin is towards the bottom, where the plugin’s methods are added to ActiveRecord::Base.

  def self.included(receiver)
    receiver.send :include, ClassMethods
    receiver.extend(ClassMethods)
  end

This method (or callback) is invoked when, to quote the Pickaxe, “the receiver is included in another module or class”. Thus, “receiver.extend(ClassMethods)” extends ActiveRecord::Base with the methods which are contained in the ClassMethods module.

OK, this makes sense, as the methods consist of the count_by_query method and a method_missing, which tests whether the called missing method ends with _count and calls count_by_query if it does. This means if you have a class method called #find_complicated_stuff you can call #find_complicated_stuff_count, which is great and makes will_paginate, for example, much easier in some edge cases.

The problem lies with the previous line: “receiver.send :include, ClassMethods”, which includes the code (and method definitions) in the ClassMethods module, which has the effect of including the methods as instance methods. This doesn’t work with Rails 2.0 for two reasons:

1) If we get a tag cloud, for example, which might look something like this:


    find(:all, {
          :select => "tags.*, COUNT(*) AS tag_count", 
          :joins => "INNER JOIN taggings",
          :conditions => "taggings.tag_id = tags.id", :group => "tags.name",
          :order => "tag_count DESC, name ASC",
          :limit => 10})

The resulting count for each of the returned tags can then be accessed through #tag_count. Except that this will be intercepted by the method_missing in the count_by_query plugin (not sure why this didn’t happen in 1.2 — perhaps to do with the load order of the plugin?)

2) When a failure occurs in the #count_from_query method because of how it works we’ve left #find in an unstable state, which means calling find (which the fixtures code does) results in doing a count. Result: kaboom!

For me the solution was to delete the offending line (looking at the specs this may be intentional behaviour rather than a bug, but I can’t really see a use for it, and certainly don’t need it).

Advertisements

Written by ctagg

December 30, 2007 at 7:08 pm