Crafting Enumerator helpers in Ruby

Among all the powerful abilities of Ruby Enumerators, one of their most useful usage is to customize what gets enumerated.

For instance, by default #each will yield the elements of the enumeration, one by one:

array = ["apple", "banana", "grape"]
array.each do |value|
  puts "${value}"
end
# "apple"
# "banana"
# "grape"

In some cases, however, we may also need the index of the element being enumerated.

For this, we can use Enumerator#with_index. It turns an existing enumerator into one that also yields the index:

array.each.with_index do |value, index|
  puts "${index}: ${value}"
end
# "1: apple"
# "2: banana"
# "3: grape"

The neat thing: this works for any enumerator! For instance, if you’re not enumerating using #each, but rather using #map or #filter, the usage is the same:

array.map.with_index do |value, index|
  "${index}. ${value.uppercase}"
end  
# ["1. APPLE", "2. BANANA", "3. GRAPE"]

How to craft your own enumerator helpers

Recently, I wanted to enumerate the pixels of an image.

The pixels are represented a single-dimensional array of integers:

image.pixels
# [998367, 251482, 4426993, 777738, ... ]

However, in my case, I want to perform different operations depending on the pixel coordinates.

Of course, we can compute the coordinates in the loop itself:

pixels.map.with_index do |pixel, i|
  x = i % image.width
  y = i / image.width
  pixel * ((x + y) / 100.0) # brighten from top-left to bottom-right
end

But there has to be a better way. What if we could substitute the enumerator’s .with_index by something like .with_coordinates?

First, I needed a quick refresher on how to write a method that enumerates on values. AppSignal’s article on Enumerators is quite a good read there.

So, our method just need to yield the values one-by-one, and that’s it? Let’s try this.

We’re going to re-open the Enumerator class, and add a #with_coordinates(width, &block) method:

class Enumerator
  def with_coordinates(width, &block)
    each.with_index do |value, i|
      x = i % width
      y = i / width
      yield value, x, y
    end
  end
end

When called, Enumerator#with_coordinates will invoke its block once for each of the enumerator values - passing the coordinates along.

Let’s see how it is used:

pixels.map.with_coordinates(image.width) do |pixel, x, y|
  pixel * ((x + y) / 100.0) # brighten from top-left to bottom-right
end

The coordinates computation are pushed away from the block, the code is nicer… Good work.

Plus #with_coordinates works not only for #each, but for any enumerator – juste like #with_index!

Method chaining on enumerators

There’s only one caveat though: in Ruby, enumerators support method chaining.

That is, instead of passing a block to the enumerator, we can instead call methods on it. Like this:

pixels
  .each
  .with_index
  .with_object("filename.png") do |pixel, i, path|
    puts "Pixel at #{path}:#{i} => #{pixel}" if i = 5
  end
# "Pixel at filename.png:5 => 1962883"

But if we try this with our current implementation of Enumerator#with_coordinates, we get:

pixels
  .each
  .with_coordinates(width)
  .with_object("filename.png") do |pixel, x, y, path|
    puts "Pixel at #{path}:#{x}:#{y} => #{pixel}" if i = 5
  end
# in `block in with_coordinates': no block given (yield)
# (LocalJumpError)

Makes sense: our helper yields to a block, but Ruby complains that none was provided.

To fix this, we’ll need to return an Enumerator instance when our #with_coordinates function is called without a block.

Let’s modify our implementation of Enumerator#with_coordinates:

class Enumerator
  def with_coordinates(width, &block)
+   if block_given?
      each.with_index do |value, i|
        x = i % width
        y = i / width
        yield value, x, y
      end
+   else
+     Enumerator.new do |y|
+       with_coordinates(width, &y)
+     end
    end
  end
end

And there we have it: using the block-less form will return a new Enumerator.

pixels.each.with_coordinates(width)
# <#Enumerator: ...>

Which means we can properly chain #with_coordinates with further methods now:

pixels
  .each
  .with_coordinates(width)
  .with_object("filename.png") do |pixel, x, y, path|
    puts "Pixel at #{path}:#{x}:#{y} => #{pixel}" if i = 5
  end
# "Pixel at filename.png:1:1 => 1962883"

And that concludes our short side-quest on implementing Enumerator helpers in Ruby. It feels very expressive; and I like how we can make our custom helpers as powerful as the native ones.

Happy enumerating!

Exploring Rails codebases: Writebook

To become a better developer, they say, read a lot of code. Although we, as a profession, often find more enjoyable to write a hundred lines of code rather than read ten, this advice stands more than ever. So let’s explore some codebases, starting with Rails applications.

Last june, 37signals released a new product, Writebook – free of charge, and full source code included1. An excellent occasion to see how the company that initiated Ruby on Rails writes code.

I’ve downloaded and run the app, explored the source code, and took some notes. Here are my takes on this code base.

What is Writebook

Writebook is a web app for publishing book-like content on the web. Books supports pages composed in Markdown, sections separators, and full-page pictures.

Writebook’s presentation on 37signals website will show you how the app looks like, and what it does. I recommend you to have a short look at this presentation, to know what we’re talking about – then come back to this article.

How to obtain the code

Writebook is free, but its code is not truly open-source. You are allowed to read the code, and to make modifications, but not to publish them, or to re-use part of the code in another product.

This means the code is not hosted in a public repository (for instance on GitHub). Instead, once you “purchase” the application (for free), you get access to a zip file containing the full source code2.

Exploring the code

The stack

Models

Controllers

Views

CSS

Javascript

Turbo

Design

Tests

Some global remarks

Conclusion

I’m amazed by the terseness and concision of the code base. Methods are short, and don’t leak complexity everywhere. That said, there’s a real business complexity in some parts: digging in the complexities of the Leafable model or the drag-dropping Javascript code can take a while. Short code isn’t always easy to read, but it feels simple, and not overwhelming.

The terseness of the code also comes from the use of Rails by Rails creators. They know the framework by heart, use it to the maximum, and push code to the framework when needed. After reading this code base, I think more of Rails as 37signal’s public web framework.

This codebase also embodies Rails as a one-person framework: a single developper, knowing the inside of the framework perfectly well, can write an ambitious web app using all the available resources Rails has to offer.

Next, I consider exploring other large Rails codebases: GitLab, Mastodon, maybe others. Let’s see where it goes.


  1. More precisely, Writebook’s source code is available – but not open source. As the FAQ states, “While you are free to review the code and make modifications to Writebook for your own use, you can not use or repurpose the code for your own purposes outside Writebook.” 

  2. The “source available” nature of Writebook means that this article cannot link to the actual code: it would have to be hosted publicly, which is not allowed under Writebook’s license. 

Véligo cargo : dimensions des caisses

En cherchant un siège-enfant qui rentre dans un Véligo cargo, je me suis rendu compte qu’il était difficile de trouver en ligne les dimensions précises de la caisse de chaque vélo (biporteur ou triporteur).

Voici donc les relevés des dimensions de chaque caisse.

Véligo biporteur

Vélo biporteur Véligo Dimensions de la caisse du vélo biporteur Véligo

Véligo triporteur

Vélo triporteur Véligo Dimensions de la caisse du vélo triporteur Véligo

Zelda: Link's Awakening progress reports moved to a new location

After two years of waiting, a new progress report for the Zelda: Link’s Awakening disassembly is finally published!

To celebrate this, I took the time to move this series of articles to its own dedicated website: the Link’s Awakening disassembly blog. Of course, the former URLs now redirect to these new pages.

This move makes subscribing to new disassembly-related articles easier, since only relevant Link’s Awakening content will be published.

And meanwhile my own blog will resume to more random and personal stuff.

Paris mon camarade – Bernard Dimey

Un poème de Bernard Dimey, que j’ai eu du mal à trouver facilement en ligne :

Paris, mon camarade, pour causer, faut connaître,
Faut s’y prom’ner la nuit, faut s’y fair’ des copains,
Faut s’offrir du bitume, en faire des kilomètres,
Y’aura toujours un pote pour t’offrir un bout de pain.
Paris, si tu connais c’est comme un’ cour d’école,
T’es tout partout chez toi si t’as l’coeur bien placé,
Si jamais t’as l’bourdon, va voir ceux qui rigolent
Et tu verras, l’soleil y en a toujours assez.

Paris, mon camarade, c’est pas tout c’qu’on raconte,
C’est pas les bulldozers, c’est pas la Tour Machin,
C’est un coeur qui s’allume au hasard des rencontres,
C’est le petit bistrot où vont tous les copains ;
Paris, si tu connais, c’est le vent dans les voiles,
Romeo et Juliette en blue-jeans à midi,
C’est le clodo Marcel qui dort sous les étoiles ;
Y a de l’Enfer, c’est sûr, mais il y du Paradis.

Paris, mon camarade, si tu connais, c’est chouette,
C’est toujours aussi bon, quand j’fous l’camp, quand j’reviens,
C’est le sourire en coin quand le cafard me guette,
C’est l’Opéra d’quat’ sous qu’est pas fait pour les chiens,
C’est le seul cinéma où y a jamais d’entracte,
Où j’ai tous mes amours et j’espère vraiment
M’offrir un soir la joie d’y jouer mon dernier acte
Et d’être parisien jusqu’au dernier moment.

Bernard Dimey, récité par Bernard Beaufrère