Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Ruby 모듈을 중첩할 때 이러한 트랩을 피하십시오.

모듈(및 클래스)은 중첩되어야 합니다. 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 모듈에서 멀리 떨어져 있습니다. 통사론. 모듈 중첩을 의도적으로 조작하려는 경우를 생각할 수 없습니다. 아시는 분이 계시다면 그 소식을 듣고 싶습니다!