I’ve just had my first patch accepted on an open source project. Quite chuffed with that! As of this weekend, the Rails calendar_helper plugin is now at version 0.21. My changes are very minimal, and only really to do with the markup.

Firstly, the default table markup’s had an overhaul. The date now goes into a %lt;caption> tag, outside the <thead>, as is appropriate. The <th>‘s in the thead now have scope="col" applied to them, again, as is appropriate.

The only other change is optional. If you pass in an option of :accessible => true, any dates in the grid which fall outside the current month will have <span> class="hidden"> monthName</span> appended to them. It could be reasonably inferred that plain numbers are dates that relate to the caption of the table, but the numbers outside the current month should probably be clarified.

You can come up with your own method of hiding content marked as .hidden; at NPG, we use the following:

.hidden {
	position:absolute;
 	left:0px;
 	top:-500px;
 	width:1px;
 	height:1px;
 	overflow:hidden;
}

but really, use whatever you’re most comfortable with.

You can get the plugin from Geoffrey Grosenbach’s subversion:

http://topfunky.net/svn/plugins/calendar_helper/

via the usual Rails plugin installation method.

As a Rubyist and Textmate user, you’ll probably be aware that def will tab-expand to stub out a method definition. You might also be aware that, for the purposes of Test::Unit, deft will tab-expand to a test method beginning def test_, allowing you to append the name of your test.

But that’s not much more help, because if we’re naming our tests properly, they’re probably going to have very_long_names, and hitting underscore all those times is a bit of a pain. So I rectified that, with this command (and it’s a command, rather than a snippet, because of all the processing it does). Pull up the commands dialog (Command-Opt-Control-C), create a new command in the Ruby bundle, and give it the following code:

#!/usr/bin/env ruby

name = STDIN.read.strip
testname = name.gsub(" ", "_").downcase
print <<OUTPUT
	def test_#{testname}
		\$0
	end
OUTPUT

The command’s Input should be “Selected Text” or “Line”; its Output should be “Insert as Snippet”. The scope should be set to source.ruby. And give it whatever key definition you want; I’ve got it on ctrl-opt-shift-t.

Usage is easy. On a new line in your test file, type the name of your test in plain English with no punctuation, eg:

get to index should list all items

and then hit your shortcut. You’ll get the following out:

def test_get_to_index_should_list_all_items

end

and your cursor will be slap bang in the middle of the test, indented, ready to write. That’s what I really want from a test definition snippet – something more than deft supplies. It’s another minute or two’s work to make it strip punctuation, so you can convert real sentences to test cases. I just decided to condition myself to save on coding on this morning’s commute.

(I wasted about half an hour on this last night, and am now kicking myself about how simple it was. Given that… definitely worth documenting the problem, so that no-one else gets held up on it.)

I’m currently working on some forum software (before you yawn: it’s not your average forum, it’s not worth the effort forking Beast, and besides, it’s a learning exercise). In this forum, when you create a topic, you also create the first post in the topic at the same time. Here’s how that happens (I don’t think I need to show the form view – the controller will suffice):

@topic = Topic.new params[:topic].merge(:user => current_user, 
                                :forum => Forum.find(params[:id]))

post = @topic.posts.build(params[:post].merge(:user => current_user))

Unfortunately, I was running into all sorts of problems – in that even though the forms were filled out and being passed to the controller, the topic wasn’t being created. This was because the post wasn’t validating. Turns out the problem was that in my post.rb, I’d said that a Post validates_presence_of :topic_id. This validation was clearly being run before the topic was created; given we’re using build to make sure the post is attached to the topic, I don’t think we need that validation. Once I stripped that out, everything worked fine.

A pretty trivial error, I know, but once my tests started failing, I had to take the application apart a little to find out what was going on. In a nutshell: I don’t think you always need to validate relationships that have to exist for the model to make any sense.

Of course, if there’s a way I can keep the validation of topic present but still build the topic/post combination as above… comments are very welcome.

0 errors, 0 failures

13 January 2007

I wrote my first proper Unit Tests in Ruby today. It felt good.

That probably sounds slightly gauche and hypocritical coming from a Rails developer. But remember – I’ve come from a front-end background; most of the time, other people make the tests; it’s up to me not to break them. I’m perfectly capable of editing tests to bring them in line with updated functionality; it just tends to have been the case that I’ve never really got my head around testing properly.

That changed recently, mainly thanks to Geoffrey Grosenbach’s excellent Peepcode screencasts. I’ve read a fair amount on testing up to now – Mark Pilgrim’s Dive Into Python is very good on the subject – but it was Geoffrey’s material that really clicked with me. (I’m watching his Capistrano one at the moment, and it’s certainly proving to be as good as the testing one).

I’ve always understood the point – and the utility – of test suites, but I’m pleased to have got my head around writing my own, starting with one (of several) projects on the back burner. Small steps at first, but it’s really satisfying to be working in a relatively test-driven manner.

And so I’ve been enjoying watching the dots fly by before those magic words come up: 0 errors, 0 failures. The Peepcode screencasts come strongly recommended, as well. Here’s to slightly more watertight code from now on.

Time to announce my first published Rails plugin: ie-specific. And, more importantly, time to explain it.

I work as a front-end developer for a large scientific publishing firm. We’re very much committed to standards compliance and accessibility, as such, make every effort to ensure our sites work in as many browsers as possible. And that includes Internet Explorer 5.

Now, prior to IE7, we did this (by and large) by producing standards-compliant stylesheets, and then adding browser hacks where they were the only solution, in order to achieve consistent look/feel in many browsers. Fast forward to IE7, and we’ve now got a problem: many browser hacks (notably * html) no longer work in IE7 and yet it still has its own raft of styling quirks.

So we’ve decided to change the way we implement browser-hacking. On the project I’m currently working on, we now have a main.css which contains all the standards-compliant, “correct” CSS. We then place the IE-specific styles inside IE conditional comments. Each browser version gets a css file of its own where necessary, named for the name of the relevant IE conditional filter – eg, lte-ie-6.css, ie-6.css, lte-ie-5.500, etcetera.

When I started work on the front-end integration for this Rails project, it was clear that this was obviously something that could be automated in Ruby – and, given that we were probably going to use it again, it made sense to package it as a plugin so that the next time I came to need it for a project, a quick script/install would be enough.

Enter ie-specific, currently hosted at Google Code given that a) it’s open-source and b) it’s an easy way to get anonymous public svn.

It’s really simple. First, install the plugin:

script/plugin install http://ie-specific-rails-plugin.googlecode.com/svn/ie-specific

Now, place any IE-specific stylesheets into #{RAILS_ROOT}/public/stylesheets/ie_specific, named with the appropriate conditional filter (eg: lte-ie-6.css). Similarly, you can place IE-specific javascript into #{RAILS_ROOT}/public/javascripts/ie_specific. We use IE-specific Javascript to simulate min/max-width capabilities in Internet Explorer.

Finally, in the <head> of your layout, include the following ERb:

<%= ie_specific_styles_and_scripts %>

Each file in the ie_specific directories will be automatically included inside appropriate conditional comments – CSS files will be imported via an @import directive, and JS files will be appropriately escaped as CDATA (important for us, because we write XHTML 1.0 strict). There’s no unnecessary duplication, either – lte-ie-6.css and lte-ie-6.js will automatically be placed inside the same conditional comment.

That’s it, really – it’s very simple, but it’s also a convenient way to keep markup and styles clean and let other developers know exactly what’s a hack and what’s not. For more info, check out the README. And let me know what you think!

So, Matt pointed out that when you do things like that, the scores for different Models are all ranked seperately because they’re coming out of different indexes. The trick, obviously, is to use the multi_search method, which generates a multi-model-index, and that’s probably a better approach. This approach is very well documented in the RDoc. So all it shows is: probably should have RTFM. The thing below isn’t by any means bad, it’s just a little like re-inventing the wheel.

Never mind, eh?

(This mail from Jonas is also worth a read on this matter).

If you’re building a Rails applciation with search, chances are you’ve run across acts_as_ferret. And if you haven’t, check it out – it allows any model to be searchable by Ferret, the Ruby port of Lucene. Ferret’s a pretty nifty search engine – reasonably fast, pretty accurate – and it’s nice to be able to use it so simply in your Rails app.

Of course, what makes acts_as_ferret really handy is that it’s neatly designed to perform multi-model searches. Or, rather, the interface to do so is there, you just have to glue the results together. After some rough stumbling about, here’s what I came up with.

Firstly: don’t use find_by_contents, that’s not much use. find_by_contents is a wrapper around find_id_by_contents. find_id_by_contents is useful because it returns you only three things: a relative score, a model name, and a model id. That means you can merge everything into a big list, and then perform individual queries on the relevant models.

So how did I implement this?

My first stab was this (borrowing some real code from what I’m working on):

articles = Article.find_id_by_contents(query)
authors = Author.find_id_by_contents(query)
matches = (articles + authors).sort_by {|match| match[:score] }

This gives you an array of model/score/id hashes, which you can then do lookups on. The problem is it’s not very DRY, and it requires editing every time you make a new model that’s Ferretable. This is my more final solution, which I came up with. This time, I’ll walk you through each step.

First, somewhere like environment.rb, define a constant array:

FERRETABLE_MODELS = %w[Article Author]

That means we can update things at a later date easily. Then, in your search controller action, start with this:

klasses = FERRETABLE_MODELS.collect {|klass| Kernelt.const_get klass}

That should give you an array containing the Article and Author objects – or whatever ActiveRecord objects you’ve chosen in your constant array. Next, let’s get the array that we arrived at last time:

matches = klasses.inject([]) {|out, klass| out << klass.find_id_by_contents(query)}.flatten

Bit more complex, but also more succinct. All this does is iterate over each klass, using an inject method with an empty array passed in, and tells each class to call find_id_by_contents, passing in the query. It then flattens that lot, so that the array is only one-deep. We're now where we were before.

Finally: let's generate an array of the actual objects we referred to, sorted by ranking. I'm going to generate an array of hashes. Each hash has two keys: :object, the actual data object we want; and :score, the rank that ferret assigned it. We get that out like so:

results = matches.collect {|match|
   :score => match[:score], 
   :object => (Kernel.const_get(match[:model]).find(match[:id]))
}.sort_by {|o| o[:score]}.reverse

Again, possibly a bit ugly. :score remains the same; we run find on the appropriate ActiveRecord model, passing in the appropriate id to obtain the :object. Finally, we sort the array by :score and flip it, so that results[0] is the most popular search record. Obviously, you can pass extra parameters into that "find" method.

All that remains is to display that lot in your view, perhaps paginate it, and build a conditional to determine how to display each kind of object.

And that's it. I made a few changes to my dummy code when typing this up, so if something's broken, tell me and I'll fix it. I think that's a more maintainable way of searching across a range of models with ferret, and it takes advantage of some useful Ruby dynamics - finding objects through Kernel.const_get, in particular. That's my Ruby fun for today, then.

Update

My colleague Ben proposes this much tidier (but untested) solution:

results = []

FERRETABLE_MODELS.each  do |klass|
  k = Kernel.const_get klass
  k.find_id_by_contents(query).each do |m|
    results.push {
       :score => m[:score], 
       :object => k.find(match[:id]) 
     }
  end
end

results = results.sort{ |a,b| b[:score] <=> a[:score] }

I like the nested loop much more - should have thought of that myself - and will admit to being lazy wrt the sort_by and .reverse trick.

So, in response to an earlier post, “mexx” asks:

Let’s say that I have a class BigTree and I have a string ‘big_tree’ how do you get the string translated to ‘BigTree’ so that I can use this translated string the way you showed?

That’s a fun challenge. Well, as I’ll later show, it’s a problem to which I already know the answer. But I gave it a shot, and came up with this – my first genuine, off-the-top-of-my-head one-liner (and I wasn’t golfing):

def camelize(str)
  str.split('_').map {|w| w.capitalize}.join
end

That’s a bit cryptic. To decode it, if Ruby’s not your first language:

  1. Take a string
  2. Split it on underscore characters into an array
  3. Capitalize each item in the array in turn (which I do by mapping the array to a new array, with the built-in String method capitalize)
  4. …and then join the whole array back into a string

Unfortunately, that works for mexxx’s example – big_tree will map to BigTree but it’s certainly not true for all possible classnames.

But, as I said earlier, I already know the answer to this. It’s part of Rails – specifically, it’s in the Inflector component of ActiveSupport. Go to the API docs and look for “camelize”. Most of the Inflector components are dependent on each other; camelize, fortunately, is only dependent on itself. And it looks like this:

def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
  if first_letter_in_uppercase
    lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
  else
    lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
  end
end

That looks a whole lot more convincing and generic. And I think that’s your answer, mexxx. So don’t thank me – thank the Rails core team. There’s lots of useful basic Ruby tools hidden in the Rails sourcecode – it’s always worth an explore, if only to find out more about how it does its stuff.

To cut a long story short: the slides for the talk I gave earlier this week are now available. You can find out more about the talk on the talks page of this site, or you can download the PDF (1.5mb). It should be fairly self-explanatory.

(A brief summary for those of you unable to scroll or click: it’s a client-side-developer’s perspective on Rails, and how to integrate client side development into the build process).

I’m going to be speaking tonight at LRUG. The talk is called “Ruby on Rails from the other side of the tracks“, and it’s about how client-side developers fit into Rails, and how you (as a back-end developer) can work with them rather than against them. If that sounds interesting (or, more to the point, you want to hear Tiest talk about Domain Specific Languages, which should be great), do come along.