developer tip

Ruby 모듈의 상수 범위

optionbox 2021. 1. 11. 08:02
반응형

Ruby 모듈의 상수 범위


mixin 모듈에서 일정한 범위에 약간의 문제가 있습니다. 내가 이런 걸 가지고 있다고합시다

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"입니다. 누구나 USER_KEY의 클래스 버전을 사용할 수있는 권한을 얻는 방법을 알고 있습니까?


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

상수는 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

여기에 간단한 해결책이 있습니다.

변경 사항 :

  • 의 존재 여부를 확인할 필요가 없습니다 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. 그래서 그것이 상수를 찾는 곳입니다. 인증에는 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.

프로젝트가 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

I'm surprised no one suggested this approach, seeing as how the OP's scenario resided within a Rails app...


There's a far simpler solution to the OP's question than the other answers here reveal:

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" 

It was @james-a-rosen's answer that gave me the inspiration to try this. I didn't want to go his route because I had several constants that are shared among several classes, each with a different value, and his method looked like a lot of typing.

ReferenceURL : https://stackoverflow.com/questions/2687276/scope-of-constants-in-ruby-modules

반응형