web-dev-qa-db-fra.com

Comment obtenir des constantes définies par la classe Module de Ruby via la réflexion?

J'essayais d'obtenir le chapitre de métaprogrammation "Ruby Programming Language" de Matz et Flanagan dans ma tête, mais je ne pouvais pas comprendre la sortie de l'extrait de code suivant que j'avais imaginé:

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"]

Le livre indique que la méthode de classe constants renvoie la liste des constantes pour la classe (comme vous pouvez le voir dans la sortie pour A.constants). J'essayais d'obtenir la liste des constantes définies pour la classe Module lorsque je suis tombé sur le comportement étrange ci-dessus.

Les constantes de A apparaissent dans Module.constants. Comment obtenir la liste des constantes définies par la classe Module?

L'état docs

Module.constants renvoie toutes les constantes définies dans le système. y compris les noms de toutes les classes et méthodes

Puisque A hérite son implémentation de Module.constants, comment se comporte-t-il différemment dans les types de base et dérivés?

p A.class               # => Class
p A.class.ancestors       # => [Class, Module, Object, Kernel]

Remarque: Si vous utilisez Ruby 1.9, constants retournerait un tableau de symboles au lieu de chaînes.

43
Gishu

Bonne question!

Votre confusion est due au fait que la méthode de classe Module.constants Masque la méthode d'instance Module#constants Pour Module.

Dans Ruby 1.9, cela a été résolu en ajoutant un paramètre facultatif:

# 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).

Dans votre exemple ci-dessus, A.constants Appelle Module#constants (La méthode d'instance), tandis que Module.constants Appelle, eh bien, Module.constants.

Dans Ruby 1.9, vous voulez donc appeler Module.constants(true).

Dans Ruby 1.8, il est possible d'appeler la méthode d'instance #constants Sur Module. Vous devez obtenir la méthode d'instance et la lier en tant que méthode de classe ( utilisant un nom différent):

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

J'aurais aimé pouvoir rétroporter complètement la fonctionnalité 1.9 à 1.8 pour ma gemme backports, mais je ne peux pas penser à un moyen d'obtenir uniquement les constantes d'un module, à l'exclusion de celles héritées, dans Ruby 1.8.

Edit: Je viens de changer la documentation officielle pour refléter correctement cela ...

56
Marc-André Lafortune

J'ai dû retourner dans ma grotte de réflexion pendant un certain temps après la réponse de Marc. Bricolage avec plus d'extraits de code, puis un peu plus. Enfin, quand la résolution de la méthode de Ruby a semblé avoir du sens, je l'ai écrite comme un article de blog pour que je n'oublie pas.

Notation: Si A " est la classe propre de [~ # ~] a [~ # ~]

Quand A.constants est appelé, la résolution de la méthode (reportez-vous à l'image dans mon article de blog pour avoir une aide visuelle) recherche les emplacements suivants dans l'ordre

  • MyClass", Object", BasicObject" (méthodes singleton)
  • Class (méthodes d'instance)
  • Module (méthodes d'instance)
  • Object (méthodes d'instance) et noyau
  • BasicObject (méthodes d'instance)

Ruby trouve la méthode d'instance Module#constants

Quand Module.constants est appelé, Ruby regarde

  • Module", Object", BasicObject" (méthodes singleton)
  • Class (méthodes d'instance)
  • Module (méthodes d'instance)
  • Object (méthodes d'instance) et noyau
  • BasicObject (méthodes d'instance)

cette fois, Ruby trouve la méthode singleton/class à Module".constants comme l'a dit Marc.

Le module définit une méthode singleton qui associe la méthode d'instance. La méthode singleton renvoie toutes les constantes connues tandis que la méthode d'instance renvoie les constantes définies dans la classe actuelle et ses ancêtres.

3
Gishu