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.