Prototype Meets Ruby: A Look at Enumerable, Array and Hash
Not to long ago, Prototype implemented some Ruby-like features, most notably Enumerable. This makes Javascript much more pleasant to deal with, but currently there is almost no documentation on how to use these features. Luckily, Sam believes in testing his code and he has some test cases lying around his darcs repository that helped me get up to speed. I’ll try to explain some of the methods in this post.
I’m going to start out with this sample data that all of the examples will be based off of. I’m also using a Javascript Logger that I’m still actively developing and thus is still very much unfinished, but for the moment it can help me get through this article.
var Fixtures = {
Products: [
{name: 'Basecamp', company: '37signals', type: 'Project Management'},
{name: 'Shopify', company: 'JadedPixel', type: 'E-Commerce'},
{name: 'Mint', company: 'Shaun Inman', type: 'Statistics'}
],
Artist: ['As I Lay Dying', '36 Crazyfist', 'Shadows Fall', 'Trivium', 'In Flames'],
Numbers: [0, 1, 4, 5, 98, 32, 12, 9]
};
var F = Fixtures;
each and friends
I used to find myself writing a lot of for loops. Although, Prototype doesn’t by any means eliminate the need to do for loops, it does give you access to what I consider to be a cleaner, easier to read method in each.
for(var i = 0; i < F.Numbers.length; i++) {
logger.info(F.Numbers[i]);
}
each allows us to iterate over these objects Ruby style.
F.Numbers.each(function(num) {
logger.info(num);
});
//Output
0
1
4
5
98
32
12
9
each takes one argument, the iterator or block in Ruby terms. This iterator is invoked once for every item in the array, and that item along with the optional index is passed to the iterator. So if we also needed the index we could do something like the code below.
F.Numbers.each(function(num, index) {
logger.info(index + ": " + num);
});
//Output
0: 0
1: 1
2: 4
3: 5
4: 98
5: 32
6: 12
7: 9
Hash key/value pairs
Hashes, created by wrapping an Object (associative array) in $H() can have their key/value pairs exposed.
$H(F.Products[0]).each(function(product) {
logger.info(product.key + ": " + product.value);
});
//Outputs
name: Basecamp
company: 37signals
type: Project Management
We can also directly access the keys and values of a Hash without iterating over it.
$H(F.Products[1]).keys();
//Outputs name,company,type
$H(F.Products[1]).values();
//Outputs Shopify,JadedPixel,E-Commerce
Make sure you don’t have a lapse of reasoning (like I did) and do keys instead of keys().
“this” inside iterators
UPDATE: Gordon commented that this is indeed available inside iterators with the use of Prototypes bind method.
I tried a similar method using bind but was unsuccessful, it turns out I had my syntax wrong. Gordon also made a small mistake in his syntax in that apply is no longer required. Below is the correct syntax that works.
F.Numbers.each(function(num, index) {
this.otherNumbers(num);
}.bind(this));
So this.otherNumbers(num) will be executed in the scope of the current object which is awesome! This was one of my major gripes against the methods that implement iterators, this is music to my ears. :-)
collect
collect allows you to iterate over an Array and return the results as a new array. Each item returned as a result of the iteration will be pushed onto the end of the new array.
var companies = F.Products.collect(function(product) {
return product.company;
});
logger.info(companies.join(', '));
// Outputs
// 37signals, JadedPixel, Shaun Inman
You can even join on the end of the block.
return F.Products.collect(function(product) {
return product.company;
}).join(', ');
include
include allows you to check if a value is included in an array and returns true or false depending on if a match was made. Assuming I put up a form asking the user to name some artist in my iTunes playlist, we could do something like the code below. Prime candidate for some conditional madness.
return F.Artists.include('Britney Spears'); // returns false thankfully ;-)
inject
inject is good for getting a collective sum from an array of values. For instance, I use it’s Ruby counterpart in a task logging application to add up all the time a user has logged on a project.
var score = F.Numbers.inject(0, function(sum, value) {
return sum + value;
});
logger.info(score);
//Output 161
The first argument to inject is just an initial value that would be added to the sum, so if we added 1 instead of 0, the output would be 162.
findAll
When given an Array, findAll will return an array of items for which the iterator evaluated to true. Basically, it allows you to build a new array of values based on some search criteria. If we wanted to find all products whose type was “E-Commerce” we could do something like the code below.
var ecom = F.Products.findAll(function(product) {
return product.type == 'E-Commerce';
});
logger.info(ecom[0].company + " produces " + ecom[0].name);
//Outputs
JadedPixel produces Shopify
Note that even if only one match is made, just as in this case, the result is still returned as an array. In that case, ecom.company would return undefined.
detect
Unlike findAll, detect will only return the first item for which the expression inside the iterator is true. So, if we wanted to find the first number that was greater than 5 we’d do something like the code below.
var low = F.Numbers.detect(function(num) {
return num > 5
});
logger.info(low);
//Outputs 98
Even though, there are other numbers above 5 in our array, detect only gives us the first match back.
invoke
invoke allows us to pass a method as a string and have that method invoked. For instance, if we wanted to sort our array of artists we’d do something like this:
[F.Artists].invoke('sort')
//Outputs 36 Crazyfist,As I Lay Dying,In Flames,Shadows Fall,Trivium
Why not just use F.Artists.sort? Well, for the example above we could do just that, but here is where invoke shines.
[F.Artists, F.Letters].invoke('sort');
//Outputs 36 Crazyfist,As I Lay Dying,In Flames,Shadows Fall,Trivium,b,c,f,h,m,r
So we invoked sort for each sub-array. Note that the code below will not work.
F.Artists.invoke('sort');
The reason this will not work is because it is taking each item in that array and trying to apply sort to it, thus if we wrote it outright, it would look something like this:
"36 Crazy Fists".sort();
We could however do something like this:
F.Artists.invoke('toLowerCase');
//Outputs 36 crazyfist,as i lay dying,in flames,shadows fall,trivium
Now, what about passing arguments to invoke? It’s fairly simple. The first argument passed to invoke is the method to be invoked, and any other arguments beyond that will be passed as arguments to the invoked method.
F.Artists.invoke('concat', " is awesome ")
//Outputs
36 Crazyfist is awesome ,As I Lay Dying is awesome ,In Flames is awesome ,Shadows Fall is awesome ,Trivium is awesome
Thats a wrap
This is getting to be pretty long, so I’m going to cut it off here and maybe come back later and fill in any holes or discuss some of the other methods. I hope this is enough to get your feet wet. I also encourage you to take a look at Sam’s test cases to find out more about Enumerable, Arrays and Hashes in Prototype.
Happy Prototyping!
Sorry, comments are closed for this article.



Discussion
DUDE! Why couldn’t you have been such a positive beacon of light when I was a Memphian! We should grab a coffee or beer when I’m in the area at Christmas time.
The code blocks are kinda hard to read with your typo theme. Regardless, thanks for the insight in this posting.
Ahaa, thanks for the insights! Prototype has a lot of hidden gems it seems ;)
Very nice overview. One suggestion, though. Prototype adds a ‘bind’ function to the Function object. Bind can enable you to access ‘this’ within your iterator.
For example, we can convert the following:var other = this.otherNumbers; F.Numbers.each(function(num, index) { logger.info(other.apply()); });to this (which is a bit cleaner):
F.Numbers.each(function(num, index) { logger.info(this.otherNumbers.apply()); }.bind(this));Gordon, thanks! I tried bind, but I had my syntax wrong, yours makes perfect sense. I’ll update the article to reflect that.
One word: sick.
Just earlier I was wishing there was a .each for PHP… now at least I have my beloved .each in Javascript.
Off-topic: When I post comments in Typo-powered weblogs, I often run into issues with my name. It spells “Björn” instead of “Björn”. Is UTF-8 breaking when posting forms through javascript in general, or does it have anything to do with prototype? Anybody knows?
Justin-
this is awesome stuff. i wasn’t aware of any of it, me being more of a ruby buff then javascripter. But this changes everything. Love it , keep it coming.
Justin: Great article! Thanks for the write-up.
Enumerable gets a little bit more interesting in JavaScript because functions (when referenced without trailing parentheses) are just regular objects. So if all you’re doing is passing every element in an array to a function, there’s no need to make a new iterator function to wrap it—just pass the function directly:
Be careful, though: if your iterator accepts more than one argument, the second argument will be the index of the array element.
Other interesting stuff: there’s an Enumerable function called
pluck, which is similar toinvokebut doesn’t make a function call. So if you have an array of DOM elements and you want to get an array of all those elements’ IDs,Finally, there’s lots of built-in JavaScript objects which act like arrays (i.e., have a
.lengthproperty and can be indexed with the[]operator) but aren’t, so they don’t have the Enumerable methods mixed in. A couple of examples of this are theargumentsobject available in every JavaScript function, and instances of theNodeListDOM class (which you get from stuff likegetElementsByTagName).Prototype calls these iterables. You can convert any iterable into an array with $A:
Thanks Sam! Also thanks for the other useful tips, extremely handy and great to know. I had glanced at pluck and seen that you had been making good use of it in Prototype, but never looked into exactly what it did. All the more reason to use Prototype :-D
I really don’t understand all the fuzz about this Prototype library…
Replacing getElementById by $ and a for loop by .each and a fresh, memory leaking, closure isn’t what makes developing javascript applications easier because that’s not the hard part.
Try abstracting the hard part, not the syntax.
Before you spend too much time on the logger, you might want to have a look at MochiKit. He has a nice screencast demo and decent documentation on the site.
Thanks for the article. -Rich
Nice overview!
I could be wrong, but I think there is a small typo with how you define the Fixtures variable. Based off the other examples in the page, it should be defined as “var F = ... ” rather than “var Fixtures = ...”
Jim, thanks for pointing that out. I had actually come back in my local code and pointed F to Fixtures and forgot to change it in the article.
I was reading the whole article thinking this was a new ruby syntax… Those improvements to Javascript are impressive!
I wanted to hate prototype.js for a long time, being a heavy DHTML developer for years – but stuff like this is making me love it.
Thanks for the writeup.
you make my life wonderful
Quick comment: the background color for this site is not set, yet all graphics assume it’s white. Looks haenous with my colorscheme. ;)
I was just going to go through prototype to learn these when I remembered your article. Nice explanations. Saved me some time having to figure it out.
Hi Justin,
I would like to discuss some things with you but they are not related to this article. They are related more to the logger.js script that you were writing. I wanted to send you an email but I just couldn’t track down your email address. I have no idea where it is hidden :) Could you please let me know…
Regards, Mandy.
Great tutorial! Had a follow-up question: is it possible to break and continue when iterating with ‘each’?
Yeah, – is there any way to make ‘break’ or ‘return; or ‘continue’ work, in an ‘each’ function?
Regarding the break or return, I think that each just invokes the function you provide for each element in the enumerable object so you could ‘continue’ by just returning the function in a given case. but I don’t see how breaking out of the entire loop not just the current iteration would work given its current structure.
Rich, look at $break and $continue. This should be what your looking for.
Justin -
Would you mind providing more information on $break and $continue? I can’t seem to turn up enough information to do anything with these…functions?
Thanks.
Rob Wilkerson: arraylist.each(function(value) { if(value == 1) throw $break; });
Great post! Just what I was looking for.
Also, I love the blog design – pleasing on the eyes.