As everybody is no doubt aware, there are new rules for getting a patch into Rails. With that in mind, I thought I’d share some of my patches here in the hopes that one of my army of readers (ha!) will find something I wrote interesting enough to try out for themselves. I’ve got a few Rails patches I want to talk about, but today I’m announcing a Prototype patch (Prototype is part of the Rails project) that I just submitted: ticket 9611.
Those of you who use Rails and Ruby will know all about Symbol#to_proc, that
bit of magic that lets you write names = users.collect(&:name) rather than the
more verbose
names = users.collect { |user| user.name }
I know, a simple example, but in some situations this improves readability like
you would not believe. I wanted a similar thing for JavaScript, and for the
example above, Prototype does just fine: users.pluck('name') is equivalent to
users.collect(function(user) { return user.name; })
But what about all the other Enumerable methods, and indeed collect itself
– why can’t it handle this situation instead of us having to use pluck? So,
taking inspiration from Ruby, I wrote a String#toFunction method that works
with both properties and methods, just like in Ruby. (Not strictly true, since
anything you expose using attr_accessor or some other getter method in Ruby is
still just a message to the receiving object, just like all other method calls.
My point is that I want the same semantic effect in JavaScript.) So, on with the
code:
String.prototype.toFunction = function() {
var property = this;
return function(o) {
var member = o[property];
return (typeof member == 'function') ? member.apply(o) : member;
};
};
What does this do? It returns a function that, given some object as an argument, returns the property of that object named by the string. So
radioInputs.findAll('checked')
is equivalent to
radioInputs.findAll(function(radio) { return radio.checked; })
(assuming we’ve modified the Enumerable methods slightly to check for a
toFunction method on their inputs). What about that member.apply business?
If it turns out that the property required is in fact a function, then that
function is executed in the context of the object to which it belongs –
member.apply(o). This means to can write things like
$$('div').findAll('visible') rather than
$$('div').findAll(function(div) { return div.visible(); })
or even ['FOO', 'BAR', 'BAZ'].map('toLowerCase'). Pretty no? I know you can do
that last one using invoke, and that invoke lets you pass arguments to the
function. My point with all this is that these special ‘do something using a
string’ methods shouldn’t be necessary – we can make all Enumerable methods
accept strings with minimal effort. Wouldn’t you like to be able to
users.sortBy('age')? I know I do.
If you think this is a good idea, why don’t you take a look at my patch and consider running the tests and giving me a +1 vote? Thanks a bunch.
