Ik heb een klein probleem met constant bereik in mixin-modules. Laten we zeggen dat ik zoiets heb
module Auth
USER_KEY = "user" unless defined? USER_KEY
def authorize
user_id = session[USER_KEY]
def
end
De USER_KEY-constante moet standaard “gebruiker” zijn, tenzij deze al is gedefinieerd. Nu zou ik dit op een aantal plaatsen kunnen mixen, maar op een van die plaatsen moet de USER_KEY anders zijn, dus misschien hebben we zoiets als dit
class ApplicationController < ActionController::Base
USER_KEY = "my_user"
include Auth
def test_auth
authorize
end
end
Ik zou verwachten dat USER_KEY “my_user” zou zijn bij gebruik in authorize, aangezien het al gedefinieerd is, maar het is nog steeds “user”, overgenomen uit de moduledefinitie van USER_KEY. Heeft iemand enig idee hoe je autorisatie kunt krijgen om de klassenversie van USER_KEY te gebruiken?
Antwoord 1, autoriteit 100%
De USER_KEY
die je hebt opgegeven (zelfs voorwaardelijk) in Auth
staat wereldwijd bekend als Auth::USER_KEY
. Het wordt niet “vermengd” met het opnemen van modules, hoewel het opnemen van modules kanverwijzen naar de sleutel op een niet-volledig gekwalificeerde manier.
Als je wilt dat elke module (bijv. ApplicationController
) zijn eigen USER_KEY
kan definiëren, probeer dan dit:
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
Als je echter al deze moeite wilt doen, kun je er net zo goed een klassenmethode van maken:
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
of een accessor op klasniveau:
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
Antwoord 2, autoriteit 78%
Constanten hebben geen globaal bereik in Ruby. Constanten kunnen vanuit elk bereik zichtbaar zijn, maar u moet specificeren waar de constante te vinden is. Wanneer u een nieuwe klasse, module of def begint, begint u een nieuwe scope, en als u een constante van een andere scope wilt, moet u specificeren waar u deze kunt vinden.
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
Antwoord 3, autoriteit 22%
Hier is een eenvoudige oplossing.
Wijzigingen:
- Het is niet nodig om te controleren op het bestaan van
USER_KEY
. - Probeer de constante op te zoeken in de module/klasse van de ontvanger (in jouw geval zou dat de controller zijn). Als het bestaat, gebruik het dan, gebruik anders de standaard module/klasse (zie hieronder wat de standaard is).
.
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
Uitleg
Het gedrag dat u ziet, is niet specifiek voor rails, maar is te wijten aan waar ruby naar constanten zoekt als het niet expliciet is vastgelegd via ::
(wat ik de “standaard” hierboven noem). Constanten worden opgezocht met behulp van de “lexicale omvang van de code die momenteel wordt uitgevoerd”. Dit betekent dat ruby eerst zoekt naar de constante in de module (of klasse) van de uitvoerende code, en dan naar buiten gaat naar elke volgende omsluitende module (of klasse) totdat hij de constante vindt die op dat bereik is gedefinieerd.
In je controller roep je authorize
aan. Maar wanneer authorize
wordt uitgevoerd, bevindt de code die momenteel wordt uitgevoerd zich in Auth
. Dus daar worden constanten opgezocht. Als Auth USER_KEY
niet had, maar een omsluitende module heeft het, dan zou de omsluitende module worden gebruikt. Voorbeeld:
module Outer
USER_KEY = 'outer_key'
module Auth
# code here can access USER_KEY without specifying "Outer::"
# ...
end
end
Een speciaal geval hiervan is de uitvoeringsomgeving op het hoogste niveau, die wordt behandeld als behorend tot de klasse Object
.
USER_KEY = 'top-level-key'
module Auth
# code here can access the top-level USER_KEY (which is actually Object::USER_KEY)
# ...
end
Een valkuil is het definiëren van een module of klasse met de scoping-operator (::
):
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
Merk op dat de constante veel later kan worden gedefinieerd dan de methode is gedefinieerd. Het opzoeken gebeurt alleen wanneer USER_KEY wordt geopend, dus dit werkt ook:
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.
Antwoord 4, autoriteit 6%
Als uw project zich in Rails bevindt, of op zijn minst de module ActiveSupport
gebruikt, kunt u de benodigde logische suiker aanzienlijk verminderen:
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
Het verbaast me dat niemand deze aanpak heeft voorgesteld, aangezien het scenario van de OP zich in een Rails-app bevond…
Antwoord 5
Er is een veel eenvoudigere oplossing voor de vraag van de OP dan de andere antwoorden hier onthullen:
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"
Het was het antwoord van @james-a-rosen dat me de inspiratie gaf om dit te proberen. Ik wilde zijn route niet volgen omdat ik verschillende constanten had die door verschillende klassen worden gedeeld, elk met een andere waarde, en zijn methode leek op veel typen.