Nested arrays and hashes
Rows of hashes
Most APIs answer with a list of objects, and Ruby reads that as an array of hashes. One row per record, one key per column, and the work is almost always grouping or sorting:
rows = [
{ n: "a", v: 2 },
{ n: "b", v: 1 },
{ n: "c", v: 2 }
]
The block variable is a hash, so row[:n] reads a column and row[:v] reads the one to group by. Ruby 3.4 lets _1 stand for the first block argument, which keeps the one-liners short.
group_by
group_by returns a hash of lists keyed by the block result, and transform_values turns each list into the values the report needs:
ruby -e 'rows = [{n: "a", v: 2}, {n: "b", v: 1}, {n: "c", v: 2}]; p rows.group_by { _1[:v] }.transform_values { |r| r.map { _1[:n] } }'
{2 => ["a", "c"], 1 => ["b"]}
The keys keep the order of first appearance, not a sorted order. Sort the result when a report should be stable: sort_by { |k, _| k } is enough for numeric keys.
Sorting by a key
sort_by with one key is the readable form; a second key in the block decides ties:
ruby -e 'rows = [{n: "a", v: 2}, {n: "b", v: 1}]; p rows.sort_by { _1[:v] }.map { _1[:n] }'
["b", "a"]
For more than one key, return an array from the block: sort_by { |row| [row[:v], row[:n]] }. Ruby 3.x sorts arrays element by element, so the first key decides and the second breaks the tie, which is what a report header implies.
What to watch for
A missing key in a row raises at the first row that lacks it, and the
error names the nil. Validate the columns once, before the loop
(rows.each { |row| row.fetch(:n) }), or use
dig when a column is optional.
Deeply nested rows are the usual place where a small class is better than
a hash. When three levels of [:a][:b][:c] appear
in a method, a Struct or a class with named
readers says the same thing and fails earlier.
Next: Enumerable by task
Article author: Arthur Isaev