Simple mapping of deeply nested collection objects
January 29th, 2008
Again, it’s time for some Ruby luv.
Coming from imperative programming background, you probably do something like the following once in a while. Now, suppose you have an instance of the class Organization, which has many departments and each department has many people working in them, and you want to get their names:
1 2 3 4 5 6 7 8 |
names_list = [] all_departments = organization.departments all_departments.each do |dep| teh_people = dep.people teh_people.each do |person| names_list << person.name end end |
After the period of Ruby-Fu apprentisiage, you’ll probably resort to map as follows:
1 2 3 |
names_list = organization.departments.map { |dep|
dep.people.map { |person| person.name }
}.flatten |
Much cleaner and more readable, which is important. However, having been used to DSL-like Ruby frameworks like RSpec and such, your imminent thought is: shouldn’t it be easy to extract that pattern and create more readable method for precisely that purpose? Yes. With recursion this is quite easy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Object def flat_map(*attrs) # pay some homage to functional programming head, *tail = attrs return self.send(head) if tail.empty? self.send(head).map { |obj| obj.flat_map(*tail) }.flatten end end # assumes the classes Person, Department and Organization accounting_staff = %w(joe jack debbie).map {|name| Person.new(name)} researchers = %w(rob alice bob).map {|name| Person.new(name)} departments = [accounting_staff, researchers].map {|ppl| Department.new(ppl)} acme = Organization.new(departments) p acme.flat_map(:departments, :people, :name) # => ["joe", "jack", "debbie", "rob", "alice", "bob"] |
There’s but one thing: Object#flat_map is actually a neat solution to wrong problem. You should know the rule tell, don’t ask or the law of the demeter.
In the example, the main object has access to organization, through which one accesses departments, through which the names
of the people are read. Usually you should avoid such cascades, as it leads to strong coupling between objects: the main
program is tied to (partial) implementation of Organization, Department and People. The better solution would be to have a method such as Organization#all_names or something similar—so that only Organization objects would know how to get all the names of the people (yes, it could ask each department for all the names as well, so that it doesn’t need to know details of the department either).
That said, I think I’ll find the use for the code every now and then.
Update 31.01.08: after brief comment by my friend and more thorough thinking over the subject, the question of whether the law of demeter applies here is related to visibility of collection objects. If the objects mapped over are just implementation details for the specific root container, then certainly the iteration over the object chain violates several design principles. However, if the collection is a hierarchy of public objects, like
it is often the case with aggregate objects. In the example, Department and Person are each public classes, likely related to other classes as well in addition to Organization.

Leave a Reply