【问题标题】:Scope of Constants in Ruby ModulesRuby 模块中的常量范围
【发布时间】:2011-02-10 20:25:15
【问题描述】:

我在 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_KEY 的模块定义。有人知道如何获得使用 USER_KEY 的类版本的授权吗?

【问题讨论】:

    标签: ruby-on-rails ruby module constants


    【解决方案1】:

    对于 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" 
    

    @james-a-rosen 的回答给了我尝试这个的灵感。我不想走他的路,因为我有几个常量在几个类之间共享,每个类都有不同的值,而且他的方法看起来需要大量输入。

    【讨论】:

      【解决方案2】:

      如果您的项目在 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 应用程序中的......

      【讨论】:

        【解决方案3】:

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

        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
        

        【讨论】:

        • 为了完整起见,你忘记了课外的X = 0puts ::X
        • @Lloeki - 答案已被编辑以包含您的评论。
        • 值得注意的是,如果你用class C::M::D; end这样的简写打开类或模块,你将无法直接访问M范围,因此M::X将是未定义的,你只能像C::M::X一样访问它。
        • 我很惊讶puts ::X 不返回2module MX 的值。那么:: 是什么意思?“顶级”?
        • 是的,:: 在常量前面的意思是 Ruby 中的“顶级”。你可以在它之后添加任何你想要的命名空间,例如示例中的 ::C::M::D::XC::M::D::X 相同,因为 C 是顶级常量。
        【解决方案4】:

        这是一个简单的解决方案。

        变化:

        • 无需检查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​​ 在没有通过 :: 明确限定范围的情况下查找常量(我在上面称之为“默认”)。使用“当前执行代码的词法范围”查找常量。这意味着 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.
        

        【讨论】:

          【解决方案5】:

          您在Auth 中声明(甚至是有条件地)的USER_KEY 在全球范围内称为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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-02-27
            • 2022-01-17
            • 1970-01-01
            • 1970-01-01
            • 2019-09-10
            • 2013-08-09
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多