Ruby模块中的常量范围

use*_*078 54 ruby module ruby-on-rails constants

我在mixin模块中有一个恒定范围的问题.假设我有类似的东西

module Auth

  USER_KEY = "user" unless defined? USER_KEY

  def authorize
    user_id = session[USER_KEY]
  def

end
Run Code Online (Sandbox Code Playgroud)

除非已经定义,否则USER_KEY常量应默认为"user".现在我可以将它混合到几个地方,但在其中一个地方USER_KEY需要不同,所以我们可能会有这样的东西

class ApplicationController < ActionController::Base

  USER_KEY = "my_user"

  include Auth

  def test_auth
    authorize
  end

end
Run Code Online (Sandbox Code Playgroud)

我希望USER_KEY在授权中使用时会是"my_user",因为它已经定义了,但它仍然是"user",取自USER_KEY的模块定义.任何人都知道如何授权使用USER_KEY的类版本?

Jam*_*sen 58

USER_KEY你宣布(甚至有条件)在Auth被全球知名Auth::USER_KEY.虽然包括模块可以以非完全合格的方式引用密钥,但它并没有"混入"包含模块.

如果您希望每个包含模块(例如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
Run Code Online (Sandbox Code Playgroud)

但是,如果你要解决所有这些问题,你可以将其作为一个类方法:

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
Run Code Online (Sandbox Code Playgroud)

或类级访问者:

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
Run Code Online (Sandbox Code Playgroud)


Fre*_*red 41

常量在Ruby中没有全局范围.常量可以在任何范围内显示,但您必须指定常量的位置.当您开始新的类,模块或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
Run Code Online (Sandbox Code Playgroud)

  • 为了完整起见,你忘了在类之外的`X = 0`和`puts :: X`. (13认同)
  • @Lloeki - 答案已经过编辑,包括您的评论. (6认同)
  • 值得注意的是,如果您使用“class C::M::D;”这样的简写打开类或模块。end`,在里面你将无法直接访问`M`范围,因此`M::X`将是未定义的,你只能像`C::M::X`一样访问它。 (2认同)

Kel*_*vin 12

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

变化:

  • 无需检查是否存在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
Run Code Online (Sandbox Code Playgroud)

说明

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

在你的控制器中,你打电话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
Run Code Online (Sandbox Code Playgroud)

这种情况的一个特例是顶级执行环境,它被视为属于类Object.

USER_KEY = 'top-level-key'
module Auth
  # code here can access the top-level USER_KEY (which is actually Object::USER_KEY)
  # ...
end
Run Code Online (Sandbox Code Playgroud)

一个缺陷是使用作用域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
Run Code Online (Sandbox Code Playgroud)

请注意,常量可以比定义的方法更晚定义.查询仅在访问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.
Run Code Online (Sandbox Code Playgroud)