modules define_method ruby-on-rails ruby module constants

ruby-on-rails - define_method - modules in ruby



Alcance de las constantes en los módulos Ruby (5)

La USER_KEY que ha declarado (incluso condicionalmente) en Auth se conoce globalmente como Auth::USER_KEY . No se "mezcla" para incluir módulos, aunque los módulos que incluyen pueden hacer referencia a la clave de una manera no calificada.

Si desea que cada módulo incluido (por ejemplo, ApplicationController ) pueda definir su propia USER_KEY , intente esto:

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

Sin embargo, si vas a tomar todas estas molestias, es mejor que lo conviertas en un método de clase:

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

o un descriptor de nivel de clase:

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

Estoy teniendo un pequeño problema con el alcance constante en los módulos mixin. Digamos que tengo algo como esto

module Auth USER_KEY = "user" unless defined? USER_KEY def authorize user_id = session[USER_KEY] def end

La constante USER_KEY debe establecerse como "usuario" a menos que ya esté definida. Ahora puedo mezclar esto en un par de lugares, pero en uno de esos lugares el USER_KEY debe ser diferente, así que podríamos tener algo como esto

class ApplicationController < ActionController::Base USER_KEY = "my_user" include Auth def test_auth authorize end end

Esperaría que USER_KEY fuera "mi_usuario" cuando se utilizara en authorize, dado que ya está definido, pero sigue siendo "user", tomado de la definición de USER_KEY de los módulos. ¿Alguien tiene alguna idea de cómo autorizar para usar la versión de clases de USER_KEY?


Aquí hay una solución simple.

Cambios:

  • No es necesario verificar la existencia de USER_KEY .
  • Intente buscar la constante en el módulo / clase del receptor (en su caso sería el controlador). Si existe, úselo, de lo contrario use el módulo / clase predeterminado (ver abajo cuál es el valor predeterminado).

.

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

Explicación

El comportamiento que está viendo no es específico de los rieles, sino que se debe a que Ruby busca constantes si no tiene un alcance explícito a través de :: (lo que yo llamo "predeterminado" más arriba). Las constantes se buscan utilizando el "alcance léxico del código que se está ejecutando actualmente". Esto significa que Ruby primero busca la constante en el módulo (o clase) del código de ejecución, luego se mueve hacia afuera a cada módulo (o clase) sucesivo de inclusión hasta que encuentra la constante definida en ese ámbito.

En tu controlador, llamas authorize . Pero cuando authorize está ejecutando, el código que se está ejecutando actualmente está en Auth . Entonces ahí es donde se buscan las constantes. Si Auth no tenía USER_KEY , pero un módulo adjunto lo tiene, entonces se usaría el que lo incluye. Ejemplo:

module Outer USER_KEY = ''outer_key'' module Auth # code here can access USER_KEY without specifying "Outer::" # ... end end

Un caso especial de esto es el entorno de ejecución de nivel superior, que se trata como perteneciente a la clase Object .

USER_KEY = ''top-level-key'' module Auth # code here can access the top-level USER_KEY (which is actually Object::USER_KEY) # ... end

Una dificultad es definir un módulo o clase con el operador de scoping ( :: :):

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

Tenga en cuenta que la constante se puede definir mucho más tarde de lo que se define el método. La búsqueda solo ocurre cuando se accede a USER_KEY, por lo que esto también funciona:

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.


Las constantes no tienen alcance global en Ruby. Las constantes pueden ser visibles desde cualquier ámbito, pero debe especificar dónde se encuentra la constante. Cuando comienza una nueva clase, módulo o definición, comienza un nuevo ámbito, y si desea una constante desde otro ámbito, debe especificar dónde encontrarlo.

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


Si su proyecto está en Rails, o al menos utiliza el módulo ActiveSupport , puede reducir significativamente la necesaria azúcar lógica:

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

Me sorprende que nadie sugiriera este enfoque, ya que la situación del OP residía en una aplicación de Rails ...


Hay una solución mucho más simple para la pregunta de OP que las otras respuestas aquí revelan:

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 => #<Bar:0x000000008be2c8> 2.3.1 :005 > r.test_it => "bar" 2.3.1 :006 > z = Baz.new => #<Baz:0x000000008658a8> 2.3.1 :007 > z.test_it => "foo"

Fue la respuesta de @ james-a-rosen la que me dio la inspiración para probar esto. No quería ir a su ruta porque tenía varias constantes que se comparten entre varias clases, cada una con un valor diferente, y su método parecía una gran cantidad de tipeo.