모듈(및 클래스)은 중첩되어야 합니다. ActiveRecord::RecordNotFound
와 같은 코드 조각 너무 일반적이어서 우리는 그것에 대해 두 번 생각하지 않습니다. 그러나 Ruby의 중첩 구현과 Rails의 자동 로드 시스템에는 코드가 이상하고 놀라운 방식으로 실패하게 만들 수 있는 몇 가지 함정이 있습니다. 이 게시물에서는 이러한 함정의 기원과 이를 방지할 수 있는 방법에 대해 설명합니다.
상수란 무엇입니까?
이 게시물은 모듈에 관한 것이지만 모듈을 이해하려면 상수를 이해해야 합니다. 대부분의 언어에서 상수는 아래 예와 같이 약간의 데이터만 저장하는 데 사용됩니다.
# These are simple constants
MAX_RETRIES = 5
DEFAULT_LANGUAGE = "en"
그러나 Ruby에서는 클래스와 모듈도 상수입니다. 나는 이것을 보여주기 위해 작은 예를 작성했습니다. 아래 모듈에는 숫자, 클래스 및 중첩 모듈의 세 가지 상수가 있습니다. 중첩된 클래스 또는 모듈에 액세스하면 루비는 간단한 숫자 상수와 동일한 규칙을 사용하여 이를 찾습니다.
module MyModule
MY_FAVORITE_NUMBER = 7
# Classes are constants
class MyClass
end
# So are modules
module MyEmbeddedModule
end
end
puts MyModule.constants.inspect # => [:MY_FAVORITE_NUMBER, :MyClass, :MyEmbeddedModule]
종종 모듈은 부모에 정의된 상수에 액세스할 수 있습니다. 그렇기 때문에 다음과 같은 코드를 작성할 수 있습니다.
module X
MARCO = "polo"
module Y
def self.n
puts MARCO
end
end
end
X::Y.n() # => "polo"
그러나 이 부모/자식 관계로 인해 Y가 X의 상수 MARCO에 액세스할 수 있다고 생각했다면 오산입니다.
일반적인 문제
위의 코드를 약간 다른 방식으로 다시 작성하면 놀라운 일이 발생합니다. Y는 더 이상 X::MARCO에 액세스할 수 없습니다. 대체 여기에서 무슨 일이 일어나고 있는 걸까요?
module A
MARCO = "polo"
end
module A::B
def self.n
puts MARCO # => uninitialized constant A::B::MARCO (NameError)
end
end
A::B.n()
자식에 의한 부모 상수의 "상속"은 부모/자식 관계로 인한 것이 아님이 밝혀졌습니다. 그것은 어휘입니다. 즉, 코드가 빌드하는 개체의 구조가 아니라 코드의 구조를 기반으로 합니다.
중첩 검사
Ruby가 중첩 상수를 검색하는 방법을 더 깊이 이해하려면 Module.nesting
을 확인하는 것이 좋습니다. 기능.
이 함수는 주어진 범위에서 상수에 대한 "검색 경로"를 구성하는 객체 배열을 반환합니다. 이전 예제의 중첩을 살펴보겠습니다.
첫 번째 예에서 중첩이 [A::B, A]
임을 알 수 있습니다. . 즉, 상수 MARCO를 사용하면 Ruby가 A::B
에서 먼저 찾습니다. , 다음 A
.
module A
MARCO = "polo"
module B
def self.n
puts Module.nesting.inspect # => [A::B, A]
puts MARCO # => "polo"
end
end
end
두 번째 예에서는 중첩에 A::B
만 포함되어 있음을 알 수 있습니다. , A
가 아님 . B가 A
의 "자식"임에도 불구하고 , 내가 작성한 코드는 중첩된 것으로 표시되지 않으므로 이 목적을 위해 중첩되지 않을 수도 있습니다.
module A
MARCO = "polo"
end
module A::B
def self.n
puts Module.nesting.inspect # => [A::B]
puts MARCO # => uninitialized constant A::B::MARCO (NameError)
end
end
Rails 자동 로드 컴플리케이션
Rails를 사용할 때 파일을 포함할 필요가 없다는 것을 알아차린 적이 있습니까? 모델을 사용하고 싶다면 그냥 사용하면 됩니다.
이것은 Rails가 자동 로드 시스템을 구현하기 때문에 가능합니다. Module.const_missing
을 사용합니다. 로드되지 않은 상수를 참조하려고 할 때 감지합니다. 그런 다음 상수가 포함되어야 한다고 생각하는 파일을 로드합니다. 대부분의 경우 작동하지만 문제가 있습니다.
Rails는 모듈에 항상 가능한 가장 큰 중첩이 있다고 가정합니다. 모듈 A::B::C에 [A::B::C, A::B, A]
중첩이 있다고 가정합니다. . 그렇지 않으면 예기치 않은 동작이 발생합니다.
아래 코드에서 모듈 B는 A::MARCO
에 액세스할 수 없어야 합니다. . 일반적인 Ruby에서는 중첩이 [A::B]이기 때문에 불가능합니다. 따라서 예외가 발생해야 합니다. 그러나 Rails의 자동 로드는 예외를 throw하지 않습니다. 대신 A::MARCO
를 반환합니다. .
# a.rb
module A
MARCO = "polo"
end
# a/b.rb
module A::B
def self.n
puts MARCO # => "polo"
end
end
# some_controller.rb
A::B.n()
결론?
이 모든 것은 생각할 것이 많습니다. 가능한 한 생각을 피하는 것을 선호하므로 A::B
모듈에서 멀리 떨어져 있습니다. 통사론. 모듈 중첩을 의도적으로 조작하려는 경우를 생각할 수 없습니다. 아시는 분이 계시다면 그 소식을 듣고 싶습니다!