J’ai un petit problème avec la scope constante des modules mixin. Disons que j’ai quelque chose comme ça
module Auth USER_KEY = "user" unless defined? USER_KEY def authorize user_id = session[USER_KEY] def end
La constante USER_KEY doit être définie par défaut sur “utilisateur” sauf si elle est déjà définie. Maintenant, je pourrais mélanger cela à quelques endroits, mais dans un de ces endroits, USER_KEY doit être différent, donc nous pourrions avoir quelque chose comme ça.
class ApplicationController < ActionController::Base USER_KEY = "my_user" include Auth def test_auth authorize end end
Je m’attendrais à ce que USER_KEY soit “my_user” lorsqu’il est utilisé dans authorize, puisqu’il est déjà défini, mais qu’il rest “utilisateur”, issu de la définition des modules USER_KEY. Quelqu’un at-il une idée de comment obtenir l’autorisation d’utiliser la version de classes de USER_KEY?
Le USER_KEY
vous avez déclaré (même conditionnellement) dans Auth
est appelé globalement Auth::USER_KEY
. Il n’est pas mélangé pour inclure des modules, bien que l’inclusion de modules puisse référencer la clé d’une manière non qualifiée.
Si vous voulez que chaque module (par exemple ApplicationController
) soit capable de définir son propre USER_KEY
, essayez ceci:
module Auth DEFAULT_USER_KEY = 'user' def self.included(base) unless base.const_defined?(:USER_KEY) base.const_set :USER_KEY, Auth::DEFAULT_USER_KEY end end def authorize user_id = session[self.class.const_get(:USER_KEY)] end end class ApplicationController < ActionController::Base USER_KEY = 'my_user' include Auth end
Si vous voulez faire face à tous ces problèmes, vous pourriez tout aussi bien en faire une méthode de classe:
module Auth DEFAULT_USER_KEY = 'user' def self.included(base) base.extend Auth::ClassMethods base.send :include, Auth::InstanceMethods end module ClassMethods def user_key Auth::DEFAULT_USER_KEY end end module InstanceMethods def authorize user_id = session[self.class.user_key] end end end class ApplicationController < ActionController::Base def self.user_key 'my_user' end end
ou un accesseur au niveau de la classe:
module Auth DEFAULT_USER_KEY = 'user' def self.included(base) base.send :attr_accessor :user_key unless base.respond_to?(:user_key=) base.user_key ||= Auth::DEFAULT_USER_KEY end def authorize user_id = session[self.class.user_key] end end class ApplicationController < ActionController::Base include Auth self.user_key = 'my_user' end
Les constantes n’ont pas de scope globale dans Ruby. Les constantes peuvent être visibles depuis n’importe quelle scope, mais vous devez spécifier où la constante doit être trouvée. Lorsque vous commencez une nouvelle classe, un nouveau module ou une nouvelle définition, vous commencez une nouvelle étendue et si vous voulez une constante d’une autre scope, vous devez spécifier où la trouver.
X = 0 class C X = 1 module M X = 2 class D X = 3 puts X # => 3 puts C::X # => 1 puts C::M::X # => 2 puts M::X # => 2 puts ::X # => 0 end end end
Voici une solution simple.
Changements:
USER_KEY
. .
module Auth USER_KEY = "user" def authorize user_key = self.class.const_defined?(:USER_KEY) ? self.class::USER_KEY : USER_KEY user_id = session[user_key] def end
Explication
Le comportement que vous voyez n’est pas spécifique aux rails, mais est dû au fait que Ruby recherche les constantes si elles ne sont pas explicitement définies via ::
(ce que j’appelle le “défaut” ci-dessus). Les constantes sont recherchées en utilisant la “scope lexicale du code en cours d’exécution”. Cela signifie que ruby cherche d’abord la constante dans le module (ou la classe) du code d’exécution, puis se déplace vers chaque module (ou classe) englobant successivement jusqu’à ce qu’il trouve la constante définie sur cette étendue.
Dans votre contrôleur, vous appelez authorize
. Mais lorsque l’ authorize
est en cours d’exécution, le code en cours d’exécution est dans Auth
. C’est donc là que les constantes sont recherchées. Si Auth n’avait pas USER_KEY
, mais qu’un module le USER_KEY
, alors celui qui le USER_KEY
serait utilisé. Exemple:
module Outer USER_KEY = 'outer_key' module Auth # code here can access USER_KEY without specifying "Outer::" # ... end end
Un cas particulier est l’environnement d’exécution de niveau supérieur, traité comme appartenant à la classe Object
.
USER_KEY = 'top-level-key' module Auth # code here can access the top-level USER_KEY (which is actually Object::USER_KEY) # ... end
Un écueil consiste à définir un module ou une classe avec l’opérateur de scope ( ::
:):
module Outer USER_KEY = 'outer_key' end module Outer::Auth # methods here won't be able to use USER_KEY, # because Outer isn't lexically enclosing Auth. # ... end
Notez que la constante peut être définie beaucoup plus tard que la méthode est définie. La recherche ne se produit que lorsque l’on accède à USER_KEY, donc cela fonctionne aussi:
module Auth # don't define USER_KEY yet # ... end # you can't call authorize here or you'll get an uninitialized constant error Auth::USER_KEY = 'user' # now you can call authorize.
Si votre projet est dans Rails, ou du moins utilise le module ActiveSupport
, vous pouvez réduire de manière significative le sucre logique nécessaire:
module Auth extend ActiveSupport::Concern included do # set a global default value unless self.const_defined?(:USER_KEY) self.const_set :USER_KEY, 'module_user' end end end class ApplicationController < ActionController::Base # set an application default value USER_KEY = "default_user" include Auth end class SomeController < ApplicationController # set a value unique to a specific controller USER_KEY = "specific_user" end
Je suis surpris que personne n'ait suggéré cette approche, vu que le scénario de l'OP se trouvait dans une application Rails ...
Il y a une solution beaucoup plus simple à la question du PO que les autres réponses révèlent ici:
module Foo THIS_CONST = 'foo' def show_const self.class::THIS_CONST end end class Bar include Foo THIS_CONST ='bar' def test_it show_const end end class Baz include Foo def test_it show_const end end 2.3.1 :004 > r = Bar.new => # 2.3.1 :005 > r.test_it => "bar" 2.3.1 :006 > z = Baz.new => # 2.3.1 :007 > z.test_it => "foo"
C’est la réponse de @james-a-rosen qui m’a inspiré pour essayer ceci. Je ne voulais pas suivre sa route car j’avais plusieurs constantes partagées entre plusieurs classes, chacune ayant une valeur différente, et sa méthode ressemblait beaucoup à la saisie.