views:

380

answers:

2

I was trying to get Matz-n-Flanagan's RPL book's 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 that 'Module.constants returns all constants defined in the system. incl. 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.

+2  A: 

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.

Marc-André Lafortune
@Marc - would appreciate it if you could go over my understanding of method resolution and confirm it. Blog post link in my post below. Thanks and Accepted :)
Gishu
A: 

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 dont 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 Kernel
  • BasicObject (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 Kernel
  • BasicObject (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.

Gishu