2010-04-21 70 views
38

我在mixin模块中存在一个恒定范围的小问题。假设我有类似的东西Ruby模块中的常量范围

module Auth 

    USER_KEY = "user" unless defined? USER_KEY 

    def authorize 
    user_id = session[USER_KEY] 
    def 

end 

除非已经定义了USER_KEY常量,否则应该默认为“user”。现在,我可能会混合到这几个地方,但这些地方的USER_KEY需要是不同的,因此我们可能有这样的事情

class ApplicationController < ActionController::Base 

    USER_KEY = "my_user" 

    include Auth 

    def test_auth 
    authorize 
    end 

end 

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

回答

50

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 

如果你打算去这一切的麻烦,不过,你还不如也只是让一个类的方法:

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 
+1

谢谢詹姆斯,那正是我正在寻找的。 – user204078 2010-04-30 00:57:29

30

常量在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 
+9

为了完整起见,您忘记了类和'puts :: X'之外的'X = 0'。 – Lloeki 2013-04-29 13:38:32

+4

@Lloeki - 答案已被编辑,包括您的评论。 – Nick 2014-03-11 21:02:22

+1

值得注意的是,如果你用'class C :: M :: D'这样的速记打开类或模块; end',你将无法直接访问'M'范围,所以'M :: X'将是未定义的,你只能像'C :: M :: X'那样访问它。 – Zequez 2016-01-02 12:15:20

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 

说明

你看到的行为是不特定的轨道,而是由于其中红宝石查找常量,如果不通过::明确范围的(我所说的“默认”以上)。使用“当前执行代码的词法范围”查找常量。这意味着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 

这方面的一个特别的例子是顶级执行环境,其被视为属于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. 
1

如果您的项目是在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 

我很惊讶,没有人提出这一做法,看到了OP的场景一个Rails应用程序中如何居住...

0

有一个简单得多的解决方案,比这里的其他答案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" 

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