2010-04-21 104 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" 

這是@詹姆斯一羅森的答案,給了我這種嘗試的靈感。我不想走他的路線,因爲我有幾個常量在幾個類中共享,每個類都有不同的值,他的方法看起來像很多輸入。