2010-04-21 4 views
38

Ich habe ein kleines Problem mit dem konstanten Umfang in Mixin-Modulen. Lassen Sie uns sagen, ich habe so etwas wie diesesBereich der Konstanten in Rubin-Modulen

module Auth 

    USER_KEY = "user" unless defined? USER_KEY 

    def authorize 
    user_id = session[USER_KEY] 
    def 

end 

Die user_key konstant „user“ in Verzug, wenn es bereits definiert ist. Nun könnte ich dies in ein paar Plätze mischen, aber in einer dieser Orte, die user_key anders sein muss, so dass wir vielleicht etwas Ähnliches erwarte ich würde

class ApplicationController < ActionController::Base 

    USER_KEY = "my_user" 

    include Auth 

    def test_auth 
    authorize 
    end 

end 

haben, dass user_key wäre „my_user“, wenn verwendet, in authorize, da es bereits definiert ist, aber immer noch "user", aus der Moduldefinition von USER_KEY. Jeder hat eine Idee, wie man autorisiert wird, die Klassenversion von USER_KEY zu verwenden?

Antwort

50

Die USER_KEY Sie erklärt (auch bedingt) in Auth ist weltweit als Auth::USER_KEY bekannt. Es wird nicht in die Einfügung von Modulen "eingemischt", obwohl die Module auf den Schlüssel in einer nicht vollständig qualifizierten Weise verweisen können.

Wenn Sie jedes einschließlich Modul (zB ApplicationController) wollen in der Lage sein, seine eigene USER_KEY zu definieren, versuchen Sie dies:

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 

Wenn Sie all diese Probleme gehen, gehen, aber Sie könnten als gut machen es nur eine Klasse Methode:

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 

oder eine Klasse-Ebene 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 
+1

Danke James, das war genau das was ich gesucht habe. – user204078

30

Konstanten haben keinen globalen Gültigkeitsbereich in Ruby. Konstanten können von jedem Bereich aus sichtbar sein, aber Sie müssen angeben, wo die Konstante gefunden werden soll. Wenn Sie mit einer neuen Klasse, einem neuen Modul oder einem neuen def beginnen, beginnen Sie einen neuen Bereich, und wenn Sie eine Konstante aus einem anderen Bereich benötigen, müssen Sie angeben, wo sie zu finden ist.

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

Der Vollständigkeit halber haben Sie 'X 'außerhalb der Klasse und' puts :: X' vergessen. – Lloeki

+4

@Lloeki - Die Antwort wurde so bearbeitet, dass sie Ihren Kommentar enthält. – Nick

+1

Es ist erwähnenswert, dass, wenn Sie die Klasse oder das Modul mit der Kurzschrift öffnen wie 'Klasse C :: M :: D; end ', drinnen kann man nicht direkt auf den' M'-Bereich zugreifen, also wäre 'M :: X' undefiniert, man könnte nur darauf zugreifen wie' C :: M :: X'. – Zequez

12

Hier ist eine einfache Lösung.

Änderungen:

  • Keine Notwendigkeit, die Existenz von USER_KEY zu überprüfen.
  • Versuchen Sie, die Konstante auf dem Modul/der Klasse des Empfängers nachzuschlagen (in Ihrem Fall wäre es der Controller). Wenn es existiert, benutze es, ansonsten verwende das Standardmodul/die Standardklasse (siehe unten für den Standard).

.

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 

Erklärung

Das Verhalten ist Sie sehen auf Schienen nicht spezifisch, ist aber aufgrund wo Rubin für Konstanten sieht, wenn nicht explizit über :: scoped (was ich die „default“ nennen über). Konstanten werden mit dem "lexikalischen Umfang des aktuell ausgeführten Codes" gesucht. Dies bedeutet, dass Ruby zuerst im Modul (oder der Klasse) des Ausführungscodes nach der Konstante sucht und dann nach außen zu jedem nachfolgenden einschließenden Modul (oder jeder Klasse) wechselt, bis es die Konstante findet, die für diesen Bereich definiert ist.

In Ihrer Steuerung rufen Sie authorize. Aber wenn authorize ausgeführt wird, ist der aktuell ausführende Code in Auth. Hier werden Konstanten gesucht. Wenn Auth nicht über USER_KEY verfügt, sondern über ein umschließendes Modul verfügt, wird das umschließende verwendet. Beispiel:

module Outer 
    USER_KEY = 'outer_key' 
    module Auth 
    # code here can access USER_KEY without specifying "Outer::" 
    # ... 
    end 
end 

Ein Spezialfall hiervon ist die Top-Level-Ausführungsumgebung, die Object als zugehörig zu der Klasse behandelt wird.

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 wird definiert, ein Modul oder eine Klasse mit dem 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 

anzumerken, dass die konstanten viel später definiert werden kann als das Verfahren definiert ist. Das Nachschlagen geschieht nur, wenn user_key zugegriffen wird, so funktioniert dies auch:

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

Wenn Ihr Projekt in Rails ist, oder zumindest nutzt das ActiveSupport Modul können Sie deutlich die notwendige Logik Zucker reduzieren:

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 

ich bin überrascht, niemand diesen Ansatz vorgeschlagen, da, wie das Szenario des OP innerhalb einer Rails-Anwendung ... gewohnt

0

Es gibt eine viel einfachere Lösung auf die Frage des OP als die anderen Antworten hier verraten:

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" 

Es war @ James-a-rosens Antwort, die mir die Inspiration gab, dies zu versuchen. Ich wollte seine Route nicht gehen, weil ich mehrere Konstanten hatte, die auf mehrere Klassen verteilt waren, jede mit einem anderen Wert, und seine Methode sah aus wie viel Tippen.