Archive for the 'Writing Reliable, Bug-Free Code' Category

form_test_helper and AJAX

Monday, April 16th, 2007

I have been using Jason Garber’s form_test_helper plugin. Basically, if you’ve ever had it where you rename a parameter in a form – change your test accordingly (post :action, :id => 1, :name => ‘George’ becoming post :action, :id => 1, :first_name => ‘George’) and forget to update your view, form_test_helper is for you. Instead you ask your controller to render your form and then call submit_form, supplying it the parameters you want. It parses your form, looks for the fields and then submits it to the Url embedded in the form tag. So the above would fail unless you actually had a field named first_name in your UI. Anyway, I thoroughly recommend it for avoiding embarrassing bugs.

However, it does not work with xhr requests. So, in Jason’s absence (he’s away at the moment), I’ll show you the changes needed here. It’s nowhere near complete but it does the trick for me.

Grab the plugin and edit vendor/plugins/form_test_helper/lib/form_test_helper.rb.

Around line 444-ish:

  # Alias for select_form when called with a block.   # Shortcut for select_form(name).submit(args) without block.  def submit_form(*args, &block)    @ix_xhr = false    internal_submit_form(*args, &block)  end

  # As submit_form but sends an XHR request  def submit_form_xhr(*args, &block)    @ix_xhr = true    internal_submit_form(*args, &block)  end

  def internal_submit_form(*args, &block)    if block_given?      select_form(*args, &block)    else      selector = args[0].is_a?(Hash) ? nil : args.shift      select_form(selector).submit(*args)    end  end

And around line 480-ish:

  def make_request(method, path, params={}, referring_uri=nil)    if self.kind_of?(ActionController::IntegrationTest)      self.send(method, path, params.stringify_keys, {:referer => referring_uri})    else      params.merge!(ActionController::Routing::Routes.recognize_path(path, :method => method))      if params[:controller] && params[:controller] != current_controller = self.instance_eval("@controller").controller_name        raise "Can't follow links outside of current controller (from #{current_controller} to #{params[:controller]})"      end      self.instance_eval("@request").env["HTTP_REFERER"] ||= referring_uri # facilitate testing of redirect_to :back      if @is_xhr        self.xhr(method, params.delete(:action), params.stringify_keys)      else        self.send(method, params.delete(:action), params.stringify_keys)      end    end  end

As I say, it’s a bit of an ugly hack but it works for me. I will submit it to Jason once I have tidied it up.

Filling your database with standing data

Thursday, March 15th, 2007

I’m just adding a new class to my application and it needs a pre-populated list of entries – standing data that will always need to be there. This is pretty easy to set up using migrations and a bit of ERb – and it even works in your test fixtures.

Firstly, create a new migration and define your model. However, do not run the migration yet.
Open your model and add a new class method (I’m using a Colour object for this example):

 def self.build_defaults   build('Red')   build('Green')   build('Blue') end

 private

 def build(name)   Color.create(:name => name) unless Colour.find_by_name(name) end

Simple enough – your build_defaults lists the standing data and build creates it, unless it already exists.

Now go back to your migration file and change it like so:

 def self.up   create_table :colours do | t |     t.column :name, :string   end

   Colour.build_defaults end

Now, when you run the migration it is automatically populated with Red, Green and Blue.

However, there’s a problem. When you run your tests, the database structure is copied but no data. You need fixtures.

Of course, you could just add the fixtures into colours.yml by hand. Easy enough for colours but what if you have something more complex? And if you add new standing data you have two places to make the change – in your model and in your fixtures.

There is an easy answer – fixture files are YAML and therefore can have ruby code (ERb) embedded in them – just like your views.

So edit colours.yml to look like this:

<% Colour.build_defaults %^gt;<% Colour.find(:all).each do | colour | %> <%= "#{colour.name.gsub(' ', '_')}" %>:   id: <%= colour.id %>   name: <%= colour.name %><% end %>

The first line builds your defaults into the test database.
The next line goes through all colours in the database and for each one generates your fixtures YAML. Firstly the fixture name – this is the colour name (with underscores instead of spaces). And next (watch the indentation) the id and name fields.

Once more, Rails makes it easy.

ARTS and Rails 1.2

Thursday, January 18th, 2007

I recently ran into some problems when upgrading an application from Rails 1.1 to Rails 1.2. RJS tests (using ARTS) that previously worked fine suddenly started failing (cannot find X in response, when I could see X in the response, right in front of me).

I dived into the ARTS code and came to the following conclusion: the JavaScriptGenerator has changed in some way; in particular, each fragment of JavaScript was separated by a newline before and is not any more. The original ARTS code would split the response into an array of strings (split by newlines) and then use Array#include? to look for the fragment. I have changed it to look like this:

def assert_rjs(action, *args, &block) if respond_to?("assert_rjs_#{action}")   send("assert_rjs_#{action}", *args) else   assert @response.body.to_s.index(create_generator.send(action, *args, &block)), generic_error(action, args) endend

In other words, instead of splitting into lines and looking for an exact match, I simply examine the entire response body for the fragment. I have reported this to Kevin Clark (he says he has had no problems with edge rails, but maybe that has changed again since 1.2); however, if you are experiencing weirdness try editing the assert_rjs method in arts/lib/arts.rb as described above.