Search

Friends

Atomspheric CO2 (PPM)

Archives

Blather

Uptime verified by Wormly.com

29 August 2008

Waiting cursor during Prototype’s Ajax.Request

In your stylesheet:

body.waiting {
  cursor: wait;
}

In your script:

document.body.addClassName('waiting');
new Ajax.Request(uri, {
  onComplete: function() {
    document.body.removeClassName('waiting');
  }
});

You probably want to be careful to make sure it's in the onComplete method and not the onSuccess method. Even better might be to create a setTimeout("document.body.removeClassName('waiting')", 3000), so you don't break the cursor for the rest of the page visit.

You also might prefer the "progress" instead of the "waiting" cursor. For other cursors and their compatibility check out QuirksMode.

22 August 2008

assert_change

The assert_difference test method in Rails is nice, but I found myself wanting a bit more flexibility. Like passing arguments to the method, not having to specify the exact change and checking for changes to things apart from numbers. So I wrote this. It gives you assert_change and assert_no_change which really just run the same thing twice. You could use a lambda sort of thing instead, which might be clearer. And it probably needs some kind of way of attaching a message. But I am mostly happy with it.

module Test::Unit::AssertChangeHelper
  def assert_change(object, *args, &block)
    old, new = before_and_after(object, *args, &block)
    assert_not_equal old, new
  end

  def assert_no_change(object, *args, &block)
    old, new = before_and_after(object, *args, &block)
    assert_equal old, new
  end

  protected
  def before_and_after(object, *args, &block)
    old = object.send(*args)
    block.call
    new = object.send(*args)
    return old, new
  end
end

You would use it like this:

assert_change(Visit, :find, :all, :conditions => ['is_exception = 1']) do
  Visit.create(options)
end

self.use_transactional_fixtures

Fixtures are slow. Even with a small project with hopeless test coverage the tests were taking over a minute. Which possibly doesn't seem like much to hardcore types, but I'm not hardcore.

I finally decided to experiment with using transactional fixtures to speed things up. In theory, I would expect it to work fine. I didn't think nested transactions were a problem. Apparently it doesn't cope with nested transactions (or possibly just certain kinds). However, you can turn it on by default and turn it off for certain tests.

So in your test_helper.rb file you can do this:

class Test::Unit::TestCase
  self.use_transactional_fixtures = true
end

and in your test for some transaction-needy controller you can do this:

class ContactsControllerTest < Test::Unit::TestCase
  self.use_transactional_fixtures = false 
  # Your tests and stuff
end

My test time dropped to 33 seconds from something like 70 seconds. And I am happier and have more time for other things like making tea at work.

12 August 2008

Apache 2, mod_passenger and HTTP Authentication

Using Lighttpd and FastCGI for Rails you can use Lighty's HTTP Authentication to "protect" an application. But the equivalent doesn't work with Apache 2. Putting the Apache authentication stuff in a <Directory> block will protect all the styles and scripts but no the application itself. You need to use a <Location> block for that.

<Location /*>
    AuthType Basic
    AuthName "Beta Testing"
    AuthUserFile /path/to/htpasswd
    Require valid-user
</Location>

9 July 2008

Unobtrusive Javascript

I've been reading a little about UJS lately, trying to figure out a nice way of building some AJAX back into the Rails project I'm working on. I had a look at the UJS plugin and tried out the Low Pro. But I'm not sure I'm convinced. PPK suggests that Javascript is to behaviour as CSS is to styling, but I disagree. CSS tends to apply to many elements in a site. Javascript is more likely to be specific to a page. Most of the benefit of CSS comes from classes and being apply to apply the same style to many things at once. It makes sense to keep something like that separate.

The is the "wrong" way to do it:

<a href="/event/new" onlick="AJAX.Request(...);return false;">Create event</a>

And this is the "right" way:

<a href="/event/new" id="create_event_link">Create event</a>
<script type="text/javascript">
//<![CDATA[
Event.addBehaviour({ '#create_event_link': Remote.Link }); // attach event listener to link
//]]>
</script>

Which is pretty verbose, and to truly do it properly you're meant to put that second block in a separate file. In a Rails application, that's going to be somewhere in public/javascripts/ which feels very much like the wrong place to put this sort of logic. The second solution will also break if the link is created dynamically after the page has loaded.

An event delegation solution might solve this particular problem. You could catch all links with the class "remote" for instance, and treat them as AJAX links. But it wouldn't be very configurable. Configurability isn't a big issue for CSS, but I think is an issue for Javascript.

I certainly understand the appeal of UJS, but I don't think the parallel between styling and behaviours is convincing. And for trivial behaviours like this I don't think duplication of code is a serious problem, either for download size or for maintenability.

27 May 2008

European Dates in Ruby on Rails

I found a solution to the problem of month/day ordering by overriding the autocasting code in ActiveRecord. However, that didn't solve the problem more generally. So I came up with a nicer solution that uses the existing Date::Format._parse_sla_eu method. It replaces the US year/month/day parsing method with the European one.

# Overrides the default Date::_parse() method for dates of format dd/mm/yyyy
# ParseDate does the typical American thing and assumes mm/dd/yyy and
# doesn't seem to be configurable

module Date::Format::EuropeanDates
  def self.included(base)
    base.class_eval do
      class << self
        alias_method :_parse_sla_us, :_parse_sla_eu
      end
    end
  end
end

Date.send(:include, Date::Format::EuropeanDates)

With thanks to Simon who showed me how to replace static methods. This is the kind of wacky stuff you could never do with PHP. Not that we should want to. It's kind of ridiculous.

13 May 2008

Rails autocasting date strings with wrong day month order

Update: I found a better solution that changes the date parsing code instead.

# Overrides the default AR date casting for dates of format dd/mm/yyyy
module ActiveRecord
  module ConnectionAdapters #:nodoc:
    class Column
      def self.fallback_string_to_date(string)
        string.gsub!(/([0-9]{2})\/([0-9]{2})\/([0-9]{4})/, '\3-\2-\1') # Convert to unambiguous date
        new_date *ParseDate.parsedate(string)[0..2]
      end
    end
  end
end

Rails ActiveRecord (or ParseDate at least) assumes that date strings with forward slashes are always in the form mm/dd/yyyy. That's all very well for silly countries like America, but not for a lot of the rest of the world.

This is my fairly shite attempt to transparently solve this problem. If you put this code in lib/autocast_date.rb for instance you can go require 'autocast_date' at the top of any model that needs it.

This will obviously break support for American style dates.

Changing human attribute labels in Rails validation messages

I've been frustrated by the lack of humanity in Rails so-called human names. So I wrote a little library, which was largely stolen from Change displayed column name in Rails validation messages. The library lets you do something a little like this...

require 'human_attributes'

class Puppy < ActiveRecord::Base
  humanize_attributes :breakfast => "Puppy's preferred breakfast"
  validates_presence_of :breakfast
end

It doesn't do anything very spectacular, but it took me a long time to work out.

# lib/human_attributes.rb

module ActiveRecord
  class Base
    # Humanize attributes
    def self.human_attribute_name(attr)
      @humanized_attributes ||= {}
      @humanized_attributes[attr.to_sym] || attr.humanize
    end

    def self.humanize_attributes(*args)
      @humanized_attributes = *args
    end
  end
end

ActiveRecord::Base#human_attribute_name is deprecated and will be replaced by proper string inflection. For those with fancy, modern humanizing Inflectors you might have to change it to something like this.

def self.humanize_attributes(*args)
  humanized_attributes = *args
  Inflector.inflections do |inflect|
    humanized_attributes.each do |attr, label|
      inflect.human attr.to_s, label
    end
  end
end

Although I couldn't get that to work, probably because my Rails isn't new enough.

0.115 seconds