2010-04-21 29 views
38

Tôi gặp sự cố nhỏ với phạm vi không đổi trong các mô-đun mixin. Giả sử tôi có một cái gì đó như thế nàyPhạm vi hằng số trong các Mô-đun Ruby

module Auth 

    USER_KEY = "user" unless defined? USER_KEY 

    def authorize 
    user_id = session[USER_KEY] 
    def 

end 

Hằng số USER_KEY phải mặc định là "người dùng" trừ khi nó đã được xác định. Bây giờ tôi có thể trộn này vào một vài nơi, nhưng ở một trong những nơi các USER_KEY cần phải được khác nhau, vì vậy chúng tôi có thể có một cái gì đó như thế này

class ApplicationController < ActionController::Base 

    USER_KEY = "my_user" 

    include Auth 

    def test_auth 
    authorize 
    end 

end 

Tôi mong rằng USER_KEY sẽ là "my_user" khi sử dụng trong ủy quyền, vì nó đã được xác định nhưng vẫn là "người dùng", được lấy từ định nghĩa mô-đun của USER_KEY. Bất cứ ai có bất kỳ ý tưởng làm thế nào để có được ủy quyền sử dụng phiên bản lớp học của USER_KEY?

Trả lời

50

Các USER_KEY bạn khai báo (thậm chí có điều kiện) trong Auth được toàn cầu biết đến như Auth::USER_KEY. Nó không được "trộn lẫn" để bao gồm các mô-đun, mặc dù bao gồm các mô-đun có thể tham chiếu khóa theo cách không đủ điều kiện.

Nếu bạn muốn mỗi bao gồm mô-đun (ví dụ ApplicationController) để có thể xác định riêng của mình USER_KEY, hãy thử này:

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 

Nếu bạn đang đi để đi đến tất cả rắc rối này, tuy nhiên, bạn cũng có thể cũng chỉ làm cho nó một phương pháp học:

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 

hoặc một accessor lớp cấp:

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

Cảm ơn James, đó là chính xác những gì tôi đang tìm kiếm. – user204078

30

Hằng số không có phạm vi toàn cầu trong Ruby. Các hằng số có thể được nhìn thấy từ bất kỳ phạm vi nào, nhưng bạn phải xác định vị trí của hằng số được tìm thấy. Khi bạn bắt đầu một lớp học mới, mô-đun, hoặc def, bạn bắt đầu một phạm vi mới, và nếu bạn muốn một hằng số từ một phạm vi khác, bạn phải xác định nơi để tìm nó.

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

Vì mục đích hoàn chỉnh, bạn đã quên 'X = 0' bên ngoài lớp và' đặt :: X'. – Lloeki

+4

@Lloeki - Câu trả lời đã được chỉnh sửa để bao gồm nhận xét của bạn. – Nick

+1

Cần lưu ý rằng nếu bạn mở lớp hoặc mô-đun bằng cách viết tắt như 'class C :: M :: D; end', bên trong bạn sẽ không thể truy cập phạm vi 'M' trực tiếp nên' M :: X' sẽ không xác định, bạn chỉ có thể truy cập nó như 'C :: M :: X'. – Zequez

12

Đây là một giải pháp đơn giản.

Thay đổi:

  • Không cần phải kiểm tra sự tồn tại của USER_KEY.
  • Cố gắng tìm kiếm hằng số trên mô-đun/lớp của người nhận (trong trường hợp của bạn, nó sẽ là bộ điều khiển). Nếu nó tồn tại, sử dụng nó, nếu không sử dụng module mặc định/lớp (xem dưới đây cho những gì mặc định là).

.

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 

Giải thích

Các hành vi bạn đang thấy là không cụ thể cho đường ray, nhưng là do nơi ruby ​​trông cho hằng số nếu không muốn nói một cách rõ ràng scoped qua :: (những gì tôi gọi là "mặc định" ở trên). Các hằng số được tra cứu bằng cách sử dụng "phạm vi từ vựng của mã hiện đang thực hiện". Điều này có nghĩa là ruby ​​đầu tiên tìm kiếm hằng số trong mô đun (hoặc lớp) của mã thực thi, sau đó chuyển ra ngoài mỗi mô-đun kèm theo liên tiếp (hoặc lớp) cho đến khi nó tìm thấy hằng số được xác định trên phạm vi đó.

Trong bộ điều khiển, bạn gọi authorize. Nhưng khi authorize đang thực thi, mã thực hiện hiện tại là Auth. Vì vậy, đó là nơi các hằng số được nhìn lên. Nếu Auth không có USER_KEY, nhưng một mô-đun kèm theo có nó, thì mô-đun kèm theo sẽ được sử dụng. Ví dụ:

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

Trường hợp đặc biệt là môi trường thực thi cấp cao nhất được coi là thuộc lớp Object.

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

Một cái bẫy được xác định một mô-đun hoặc lớp học với các nhà điều hành Phạm vi (::):

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 

Lưu ý rằng hằng số có thể được định nghĩa nhiều muộn hơn phương pháp được định nghĩa. Tra cứu chỉ xảy ra khi USER_KEY được truy cập, vì vậy công trình này quá:

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

Nếu dự án của bạn là trong Rails, hoặc ít nhất là sử dụng các mô-đun ActiveSupport, bạn có thể làm giảm đáng kể các đường logic cần thiết:

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 

Tôi ngạc nhiên không ai đề xuất cách tiếp cận này, xem như thế nào kịch bản của OP cư trú trong một ứng dụng Rails ...

0

Có một giải pháp đơn giản hơn nhiều so với câu trả lời khác ở đây tiết lộ:

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" 

Đó là câu trả lời của @ james-a-rosen đã cho tôi cảm hứng để thử điều này. Tôi không muốn đi tuyến đường của mình bởi vì tôi có một số hằng số được chia sẻ giữa nhiều lớp, mỗi lớp có một giá trị khác nhau, và phương pháp của anh ấy trông giống như rất nhiều cách gõ.

Các vấn đề liên quan