简体   繁体   English

Ruby模块中的常量范围

[英]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: 变化:

  • No need to check for existence of USER_KEY . 无需检查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). 如果存在,请使用它,否则使用默认模块/类(请参阅下面的默认模块)。

.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM