I found an interesting but unexplained alternative to an accepted answer. The code clearly works in the REPL. For example:
module
Your parse of the idiom is pretty spot on, but I'll go through it and try to clear up any questions you mentioned.
Foo.constants
As you mentioned, this returns an array of module constant names as symbols.
Array#map
You obviously know what this does, but I want to include it for completeness. Map takes a block and calls that block with each element as an argument. It returns an Array
of the results of these block calls.
Object#method
Also as you mentioned, this does a method lookup. This is important because a method without parentheses in Ruby is a method call of that method without any arguments.
&
This operator is for converting things to blocks. We need this because blocks are not first-class objects in Ruby. Because of this second-class status, we have no way to create blocks which stand alone, but we can convert Procs
into blocks (but only when we are passing them to a function)! The &
operator is our way of doing this conversion. Whenever we want to pass a Proc
object as if it were a block, we can prepend it with the &
operator and pass it as the last argument to our function. But &
can actually convert more than just Proc
objects, it can convert anything that has a to_proc
method!
In our case, we have a Method
object, which does have a to_proc
method. The difference between a Proc
object and a Method
object lies in their context. A Method
object is bound to a class instance and has access to the variables which belong to that class. A Proc
is bound to the context in which it is created; that is, it has access to the scope in which it was created. Method#to_proc
bundles up the context of the method so that the resulting Proc
has access to the same variables. You can find more about the &
operator here.
grep(Class)
The way Enumerable#grep
works is that it runs argument === x
for all x in the enumerable. The ordering of the arguments to ===
is very important in this case, since it's calling Class.===
rather than Foo::Bar.===
. We can see the difference between these two by running:
irb(main):043:0> Class === Foo::Bar
=> true
irb(main):044:0> Foo::Bar === Class
=> false
Module#===
(Class
inherits its ===
method from Method
) returns True
when the argument is an instance of Module
or one of its descendants (like Class
!), which will filter out constants which are not of type Module
or Class
.
You can find the documentation for Module#===
here.