Ruby 模块中常量的范围

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2687276/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 22:34:33  来源:igfitidea点击:

Scope of Constants in Ruby Modules

ruby-on-railsrubymoduleconstants

提问by user204078

I'm having a little problem with constant scope in mixin modules. Let's say I have something like this

我在 mixin 模块中的常量范围有一个小问题。假设我有这样的事情

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. 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 常量应默认为“user”。现在我可能会将它混合到几个地方,但在其中一个地方 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. Anyone have any idea how to get authorize to use the classes version of USER_KEY?

我希望 USER_KEY 在授权中使用时将是“my_user”,因为它已经定义,但它仍然是“用户”,取自 USER_KEY 的模块定义。任何人都知道如何获得使用 USER_KEY 的类版本的授权?

回答by James A. Rosen

The USER_KEYyou declared (even conditionally) in Authis globally known as Auth::USER_KEY. It doesn't get "mixed in" to including modules, though including modules canreference the key in a non-fully-qualified fashion.

USER_KEY你宣布(甚至有条件)在Auth被全球知名Auth::USER_KEY。它不会“混入”到包含模块中,尽管包含模块可以以非完全限定的方式引用键。

If you want each including module (e.g. 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

回答by Fred

Constants don't have global scope in 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.

常量在 Ruby 中没有全局作用域。常量可以在任何范围内可见,但您必须指定在哪里可以找到常量。当你开始一个新的类、模块或定义时,你开始了一个新的作用域,如果你想要一个来自另一个作用域的常量,你必须指定在哪里找到它。

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

回答by Kelvin

Here's a simple solution.

这是一个简单的解决方案。

Changes:

变化:

  • No need to check for existence of USER_KEY.
  • Try to look up the constant on the receiver's module/class (in your case it would be the controller). If it exists, use it, otherwise use the default module/class (see below for what the default is).
  • 无需检查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). 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.

您看到的行为并非特定于 rails,而是由于 ruby​​ 在没有明确限定范围::的情况下查找常量的位置(我在上面称之为“默认”)。使用“当前执行代码的词法范围”查找常量。这意味着 ruby​​ 首先在执行代码的模块(或类)中查找常量,然后向外移动到每个连续的封闭模块(或类),直到找到在该范围内定义的常量。

In your controller, you call authorize. But when authorizeis executing, the currently executing code is in 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. Example:

在您的控制器中,您调用authorize. 但是在authorize执行时,当前正在执行的代码在Auth. 这就是查找常量的地方。如果 Auth 没有USER_KEY,但一个封闭的模块有它,那么封闭的将被使用。例子:

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.

这种情况的一个特例是顶级执行环境,它被视为属于 class 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.

回答by Frank Koehl

If your project is in Rails, or at least utilizes the ActiveSupportmodule, 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 应用程序中的……

回答by Derrell Durrett

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. 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.

@james-a-rosen 的回答给了我尝试这个的灵感。我不想走他的路线,因为我有几个常量在几个类之间共享,每个类都有不同的值,而且他的方法看起来像很多打字。