The main reason for doing this is that I wasn't sure whether the text-to-speech engine would handle "1st" "2nd" properly, so I wanted to make sure I   I just tried it and it does appear to work, so I will switch to a function.

Your Ruby example isn't really necessary - Ruby on Rails at least has a built in ordinalize.