[英]Scope of Constants in Ruby Modules
I'm having a little problem with constant scope in mixin modules. 我在mixin模块中有一个恒定范围的问题。 Let's say I have something like this
假设我有类似的东西
module Auth
USER_KEY = "user" unless defined? USER_KEY
def authorize
user_id = session[USER_KEY]
def
end
The USER_KEY constant should default to "user" unless it's already defined. 除非已经定义,否则USER_KEY常量应默认为“user”。 Now I might mix this into a couple of places, but in one of those places the USER_KEY needs to be different, so we might have something like this
现在我可以将它混合到几个地方,但在其中一个地方USER_KEY需要不同,所以我们可能会有这样的东西
class ApplicationController < ActionController::Base
USER_KEY = "my_user"
include Auth
def test_auth
authorize
end
end
I would expect that USER_KEY would be "my_user" when used in authorize, since it's already defined, but it's still "user", taken from the modules definition of USER_KEY. 我希望USER_KEY在授权中使用时会是“my_user”,因为它已经定义了,但它仍然是“user”,取自USER_KEY的模块定义。 Anyone have any idea how to get authorize to use the classes version of USER_KEY?
任何人都知道如何授权使用USER_KEY的类版本?
The USER_KEY
you declared (even conditionally) in Auth
is globally known as Auth::USER_KEY
. 您在
Auth
声明(甚至有条件地)的USER_KEY
全局称为Auth::USER_KEY
。 It doesn't get "mixed in" to including modules, though including modules can reference the key in a non-fully-qualified fashion. 虽然包括模块可以以非完全合格的方式引用密钥,但它并没有“混入”包含模块。
If you want each including module (eg ApplicationController
) to be able to define its own USER_KEY
, try this: 如果您希望每个包含模块(例如
ApplicationController
)能够定义自己的USER_KEY
,请尝试以下方法:
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
If you're going to go to all this trouble, though, you might as well just make it a class method: 但是,如果你要解决所有这些问题,你可以将其作为一个类方法:
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
or a class-level accessor: 或类级访问者:
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
Constants don't have global scope in Ruby. 常量在Ruby中没有全局范围。 Constants can be visible from any scope, but you must specify where the constant is to be found.
常量可以在任何范围内显示,但您必须指定常量的位置。 When you begin a new class, module, or def, you begin a new scope, and if you want a constant from another scope, you have to specify where to find it.
当您开始新的类,模块或def时,您开始一个新的范围,如果您想要另一个范围的常量,您必须指定在哪里找到它。
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
Here's a simple solution. 这是一个简单的解决方案。
Changes: 变化:
USER_KEY
. 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
Explanation 说明
The behavior you're seeing isn't specific to rails, but is due to where ruby looks for constants if not explicitly scoped via ::
(what I call the "default" above). 你所看到的行为并不是特定于rails,而是由于ruby查找常量,如果没有通过
::
:(我称之为上面的“默认”)显式作用域。 Constants are looked up using the "lexical scope of the currently executing code". 使用“当前正在执行的代码的词法范围”查找常量。 This means that ruby first looks for the constant in the executing code's module (or class), then moves outward to each successive enclosing module (or class) until it finds the constant defined on that scope.
这意味着ruby首先在执行代码的模块(或类)中查找常量,然后向外移动到每个连续的封闭模块(或类),直到找到在该范围上定义的常量。
In your controller, you call authorize
. 在您的控制器中,您调用
authorize
。 But when authorize
is executing, the currently executing code is in Auth
. 但是当
authorize
正在执行时,当前正在执行的代码在Auth
。 So that is where constants are looked up. 这就是查找常量的地方。 If Auth didn't have
USER_KEY
, but an enclosing module has it, then the enclosing one would be used. 如果Auth没有
USER_KEY
,但是封闭模块有,那么将使用封闭的模块。 Example: 例:
module Outer
USER_KEY = 'outer_key'
module Auth
# code here can access USER_KEY without specifying "Outer::"
# ...
end
end
A special case of this is the top-level execution environment, which is treated as belonging to class Object
. 这种情况的一个特例是顶级执行环境,它被视为属于类
Object
。
USER_KEY = 'top-level-key'
module Auth
# code here can access the top-level USER_KEY (which is actually Object::USER_KEY)
# ...
end
One pitfall is defining a module or class with the 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
Note that the constant can be defined much later than the method is defined. 请注意,常量可以比定义的方法更晚定义。 The lookup only happens when USER_KEY is accessed, so this works too:
查询仅在访问USER_KEY时发生,因此这也适用:
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.
If your project is in Rails, or at least utilizes the ActiveSupport
module, you can significantly reduce the necessary logic sugar: 如果您的项目在Rails中,或者至少使用
ActiveSupport
模块,则可以显着减少必要的逻辑糖:
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
I'm surprised no one suggested this approach, seeing as how the OP's scenario resided within a Rails app... 我很惊讶没有人建议这种方法,看看OP的场景如何在Rails应用程序中存在......
There's a far simpler solution to the OP's question than the other answers here reveal: OP问题的解决方案远比其他答案显示的简单得多:
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"
It was @james-a-rosen's answer that gave me the inspiration to try this. @ james-a-rosen的回答给了我尝试这个的灵感。 I didn't want to go his route because I had several constants that are shared among several classes, each with a different value, and his method looked like a lot of typing.
我不想去他的路线,因为我有几个常数在几个类之间共享,每个都有不同的值,他的方法看起来像很多打字。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.