I think I encountered Enumerable#group_by in real code for the first time.
It's easiest to think about this, at first, in terms of an Array.
The method is a way to group the elements of the array by some condition of the elements' properties. It groups them into a hash, such that the "groups" are represented as the keys of the hash, and the groups of elements that correspond to each group are represented as that key's value. (So, the values are often (always?) Arrays).
In simpler terms. #group_by takes an Array, which is just one big container of stuff (or: a collection). It wants to sort that Array into many smaller containers (or collections), based on a given condition (which it takes as a block).
Every time #group_by iterates through the block you give it, it does two things: it evaluates the result of the block and assigns that result to a key; and it takes the element of the Array that got yielded to the block and puts it in a new Array.
This new Array is a value of the hash that #group_by is constructing. The key for this value is the result of the block when it was evaluated with the element that is getting put in the new Array.
The block evaluates to different results depending on the elements it receives. This is how #group_by divvies up the original collection into a set of smaller collections.
Several elements in the collection may evaluate to the same result when passed to the block. In that case, they will all be part of the same hash value, the value whose key is that result.
If all of the elements in the collection wind up in the same hash value, then you haven't split the collection up at all.
The example in the Ruby docs shows how to split up a collection of Integers based on their remainders when divided by 3:
(1..6).group_by { |i| i%3 } #=> {0=>[3, 6], 1=>[1, 4], 2=>[2, 5]}
(This is a range, here, but the equivalent to that is the Array [1, 2, 3, 4, 5, 6].)
The original collection is the set of continuous Integers from 1 to 6. The condition to split the collection on, represented by the block, is i % 3.
So in the terms stated above, the "condition of the elements' properties," the property in question is "divisibility", and the condition is "divisibility by 3." We want a hash with groups of elements separated by their divisibility by 3.
Thus, the results of the block when passed the elements in the collection are the set of the possible remainders when an Integer is divided by 3: [0, 1, 2] (which you can get by calling #keys on the computed hash).
The values associated with these hash keys are the sets of elements from the original collection. Each key has an Array as its value, the Array of original elements that, when passed to the block, evaluate to that key. For example, i % 3 results in 0 for the elements 3 and 6, so the key 0 points to [3, 6].
If there is a duplicate value in the original Array, there will also be a duplicate value in the hash value of the key that corresponds to the duplicate value. So if you don't want those duplicates to propagate into your hash, you should call #uniq prior to calling #group_by (probably in the same chain, i.e., array.uniq.group_by).
And that's how to understand Enumerable#group_by when the Enumerable is an Array.