Ruby模块中的常量范围

在mixin模块中,我遇到了一个恒定范围的问题。 比方说,我有这样的东西

module Auth USER_KEY = "user" unless defined? USER_KEY def authorize user_id = session[USER_KEY] def end 

USER_KEY常量应该默认为“用户”,除非已经定义。 现在我可以将它们混合到一些地方,但是在其中一个地方USER_KEY需要不同,所以我们可能会有这样的东西

 class ApplicationController < ActionController::Base USER_KEY = "my_user" include Auth def test_auth authorize end end 

我期望USER_KEY在授权中使用时是“my_user”,因为它已经被定义,但是它仍然是“user”,取自USER_KEY的模块定义。 任何人有任何想法如何获得授权使用USER_KEY的类版本?

您在Auth声明(甚至是有条件地)的USER_KEY全局称为Auth::USER_KEY 。 它没有被“混入”到包含模块,尽pipe包括模块可以以非完全合格的方式引用密钥。

如果你希望每个包含模块(例如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 

如果你要去解决这个问题,那么你可以把它变成一个类的方法:

 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 

或者是一个级别的访问者:

 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 

常量在Ruby中没有全局作用域。 常量可以在任何范围内可见,但您必须指定常量的位置。 当你开始一个新的类,模块或def时,你开始一个新的范围,如果你想从另一个范围的常量,你必须指定在哪里find它。

 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 

这是一个简单的解决scheme。

变化:

  • 不需要检查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 

说明

你看到的行为不是专门针对rails的,但是由于ruby寻找常量的地方,如果不是通过:: :(我称之为“default”)来显式限定的话。 常量使用“当前正在执行的代码的词法范围”查找。 这意味着ruby首先在执行代码的模块(或类)中查找常量,然后向外移动到每个连续的封闭模块(或类),直到find在该范围内定义的常量。

在你的控制器中,你可以调用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 

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

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

一个陷阱就是用范围操作符( :: :)定义一个模块或类:

 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 

请注意,该常量可以定义比方法定义晚得多。 查询仅在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. 

如果您的项目在Rails中,或者至less使用ActiveSupport模块,则可以显着减less必要的逻辑加糖:

 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 

我很惊讶没有人提出这种方法,看到OP的情况如何在一个Rails应用程序中…

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" 

这是@詹姆斯·罗森的答案,给了我这种尝试的灵感。 我不想走他的路,因为我有几个常量,在几个类中共享,每个类都有不同的值,他的方法看起来像很多input。