I was trying to get Matz and Flanagan's "Ruby Programming Language" metaprogramming chapter into my head, However I couldn't understand the output from the following code snippet that I dreamed up:
p Module.constants.length # => 88
$snapshot1 = Module.constants
class A
NAME=:abc
$snapshot2 = Module.constants
p $snapshot2.length # => 90
p $snapshot2 - $snapshot1 # => ["A", "NAME"]
end
p Module.constants.length # => 89
p Module.constants - $snapshot1 # => ["A"]
p A.constants # => ["NAME"]
The book states that the class method constants
returns the list of constants for the class (as you can see in the output for A.constants
).
I was trying to get the list of constants defined for the Module class when I came across the above strange behavior.
A
's constants show up in Module.constants. How do I get the list of constants defined by the Module class?
The docs state
Module.constants
returns all constants defined in the system. including names of all classes and methods
Since A
inherits its implementation from Module.constants
, how does it behave differently in the base and derived types?
p A.class # => Class
p A.class.ancestors # => [Class, Module, Object, Kernel]
Note: If you're using Ruby 1.9, constants
would return an array of symbols instead of strings.
Good question!
Your confusion is due to the fact that the class method Module.constants
hides the instance method Module#constants
for Module
.
In Ruby 1.9, this has been addressed by adding an optional parameter:
# No argument: same class method as in 1.8:
Module.constants # ==> All constants
# One argument: uses the instance method:
Module.constants(true) # ==> Constants of Module (and included modules)
Module.constants(false) # ==> Constants of Module (only).
In your example above, A.constants
calls Module#constants
(the instance method), while Module.constants
calls, well, Module.constants
.
In Ruby 1.9, you thus want to call Module.constants(true)
.
In Ruby 1.8, it is possible to call the instance method #constants
on Module
. You need to get the instance method and bind it as a class method (using a different name):
class << Module
define_method :constants_of_module, Module.instance_method(:constants)
end
# Now use this new class method:
class Module
COOL = 42
end
Module.constants.include?("COOL") # ==> false, as you mention
Module.constants_of_module # ==> ["COOL"], the result you want
I wish I was able to backport the 1.9 functionality completely to 1.8 for my backports
gem, but I can't think of a way to get only the constants of a Module, excluding the inherited ones, in Ruby 1.8.
Edit: Just changed the official documentation to correctly reflect this...