Reputation: 136613
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.
Upvotes: 47
Views: 40206
Reputation: 136613
I had to go back into my thinking cave for a while after Marc's response. Tinkered with more code snippets and then some more. Finally when Ruby's method resolution seemed to make sense wrote it down as a blog post so that I don't forget.
Notation: If A" is the eigenclass of A
When A.constants
is called, method resolution (refer to the image in my blog post to have a visual aid) looks up the following locations in order
MyClass"
, Object"
, BasicObject"
(singleton methods)Class
(instance methods)Module
(instance methods)Object
(instance methods) and KernelBasicObject
(instance methods)Ruby finds the instance method Module#constants
When Module.constants
is called, Ruby looks at
Module"
, Object"
, BasicObject"
(singleton methods)Class
(instance methods)Module
(instance methods)Object
(instance methods) and KernelBasicObject
(instance methods)this time, Ruby finds the singleton/class method at Module".constants
as Marc said.
Module defines a singleton method which shadows the instance method. The singleton method returns all known constants whereas the instance method returns the constants defined in current class and its ancestors.
Upvotes: 3
Reputation: 79562
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...
Upvotes: 65