자동 로드 및 상수 릴로드

이 가이드는 zeitwerk 모드에서 자동 로드 및 릴로드가 작동하는 방식을 문서화합니다.

이 가이드를 읽고 나면 다음을 알 수 있습니다:

  • 관련 Rails 구성
  • 프로젝트 구조
  • 자동 로드, 릴로드 및 적극적인 로드
  • 단일 테이블 상속
  • 그리고 더 많은 것들

소개

정보. 이 가이드는 Rails 애플리케이션의 자동 로드, 릴로드 및 적극적인 로드를 문서화합니다.

일반적인 Ruby 프로그램에서는 사용하려는 클래스와 모듈을 정의하는 파일을 명시적으로 로드합니다. 예를 들어, 다음 컨트롤러는 ApplicationControllerPost를 참조하며, 일반적으로 이에 대한 require 호출을 수행해야 합니다:

# 이렇게 하지 마세요.
require "application_controller"
require "post"
# 이렇게 하지 마세요.

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

Rails 애플리케이션에서는 애플리케이션 클래스와 모듈이 require 호출 없이 어디서나 사용 가능합니다:

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

Rails는 필요한 경우 대신 이를 자동 로드합니다. 이것이 가능한 이유는 Rails가 대신 설정하는 Zeitwerk 로더 덕분입니다. 이 로더는 자동 로드, 릴로드 및 적극적인 로드를 제공합니다.

한편, 이 로더는 다른 것들을 관리하지 않습니다. 특히 Ruby 표준 라이브러리, 젬 종속성, Rails 구성 요소 자체 또는 (기본적으로) 애플리케이션 lib 디렉토리를 관리하지 않습니다. 해당 코드는 일반적으로 로드되어야 합니다.

프로젝트 구조

Rails 애플리케이션에서 파일 이름은 정의하는 상수와 일치해야 합니다. 디렉토리는 네임스페이스 역할을 합니다.

예를 들어, app/helpers/users_helper.rb 파일은 UsersHelper를 정의해야 하고, app/controllers/admin/payments_controller.rb 파일은 Admin::PaymentsController를 정의해야 합니다.

기본적으로 Rails는 Zeitwerk가 파일 이름을 String#camelize로 변환하도록 구성합니다. 예를 들어, "users_controller".camelize가 반환하는 것과 같은 UsersController를 정의하는 것으로 예상합니다.

아래 Customizing Inflections 섹션에서는 이 기본값을 재정의하는 방법을 문서화합니다.

자세한 내용은 Zeitwerk 문서를 확인하세요.

config.autoload_paths

자동 로드 및 (선택적으로) 릴로드되는 애플리케이션 디렉토리 목록을 자동 로드 경로라고 합니다. 예를 들어, app/models. 이러한 디렉토리는 최상위 네임스페이스: Object를 나타냅니다.

정보. 자동 로드 경로는 Zeitwerk 문서에서 루트 디렉토리라고 하지만, 이 가이드에서는 “자동 로드 경로"라는 용어를 사용합니다.

자동 로드 경로 내에서 파일 이름은 여기에 문서화된 대로 정의하는 상수와 일치해야 합니다.

기본적으로 애플리케이션의 자동 로드 경로는 애플리케이션이 부팅될 때 존재하는 app 하위 디렉토리 전체(단, assets, javascriptviews는 제외)와 종속된 엔진의 자동 로드 경로로 구성됩니다.

예를 들어, UsersHelperapp/helpers/users_helper.rb에 구현되어 있는 경우 모듈은 자동 로드 가능하므로 require 호출을 작성할 필요가 없습니다:

$ bin/rails runner 'p UsersHelper'
UsersHelper

Rails는 app 아래의 사용자 정의 디렉토리를 자동 로드 경로에 자동으로 추가합니다. 예를 들어, 애플리케이션에 app/presenters가 있는 경우 프레젠터를 자동 로드하기 위해 아무것도 구성할 필요가 없습니다. 기본적으로 작동합니다.

기본 자동 로드 경로 배열은 config/application.rb 또는 config/environments/*.rb에서 config.autoload_paths에 추가하여 확장할 수 있습니다. 예를 들어:

module MyApplication
  class Application < Rails::Application
    config.autoload_paths << "#{root}/extras"
  end
end

또한 엔진은 엔진 클래스의 본문과 자체 config/environments/*.rb에 추가할 수 있습니다.

경고. ActiveSupport::Dependencies.autoload_paths를 변경하지 마세요. 자동 로드 경로를 변경하는 공개 인터페이스는 config.autoload_paths입니다.

경고: 애플리케이션이 부팅되는 동안 자동 로드 경로에서 코드를 자동 로드할 수 없습니다. 특히 config/initializers/*.rb에서 직접 수행할 수 없습니다. 유효한 방법은 아래 애플리케이션 부팅 시 자동 로드 섹션을 확인하세요.

자동 로드 경로는 Rails.autoloaders.main 자동 로더에 의해 관리됩니다.

config.autoload_lib(ignore:)

기본적으로 lib 디렉토리는 애플리케이션이나 엔진의 자동 로드 경로에 속하지 않습니다.

config.autoload_lib 구성 메서드는 lib 디렉토리를 config.autoload_pathsconfig.eager_load_paths에 추가합니다. config/application.rb 또는 config/environments/*.rb에서 호출해야 하며 엔진에서는 사용할 수 없습니다.

일반적으로 lib에는 자동 로더가 관리해서는 안 되는 하위 디렉토리가 있습니다. 필수 ignore 키워드 인수에 해당 이름을 상대 경로로 전달하세요. 예를 들어:

config.autoload_lib(ignore: %w(assets tasks))

왜? assetstasks는 일반 Ruby 코드와 lib 디렉토리를 공유하지만, 해당 내용은 릴로드되거나 적극적으로 로드되어서는 안 됩니다.

.rb 확장자가 없거나 릴로드되거나 적극적으로 로드되어서는 안 되는 모든 lib 하위 디렉토리를 ignore 목록에 포함해야 합니다. 예를 들어,

config.autoload_lib(ignore: %w(assets tasks templates generators middleware))

config.autoload_lib는 7.1 이전에는 사용할 수 없지만, 애플리케이션이 Zeitwerk를 사용하는 한 다음과 같이 모방할 수 있습니다:

# config/application.rb
module MyApp
  class Application < Rails::Application
    lib = root.join("lib")

    config.autoload_paths << lib
    config.eager_load_paths << lib

    Rails.autoloaders.main.ignore(
      lib.join("assets"),
      lib.join("tasks"),
      lib.join("generators")
    )

    # ...
  end
end

config.autoloadoncepaths

클래스와 모듈을 자동 로드할 수 있지만 릴로드되지 않도록 하고 싶을 수 있습니다. autoload_once_paths 구성은 자동 로드될 수 있지만 릴로드되지 않는 코드를 저장합니다.

기본적으로 이 컬렉션은 비어 있지만 config.autoload_once_paths에 추가하여 확장할 수 있습니다. config/application.rb 또는 config/environments/*.rb에서 이렇게 할 수 있습니다. 예를 들어:

module MyApplication
  class Application < Rails::Application
    config.autoload_once_paths << "#{root}/app/serializers"
  end
end

또한 엔진은 엔진 클래스의 본문과 자체 config/environments/*.rb에 추가할 수 있습니다.

정보. app/serializersconfig.autoload_once_paths에 추가되면 app 아래의 사용자 정의 디렉토리라는 기본 규칙이 더 이상 적용되지 않습니다. 이 설정이 해당 규칙을 재정의합니다.

이것은 캐시된 곳에 저장되는 클래스와 모듈에 중요합니다. 예를 들어 Rails 프레임워크 자체.

예를 들어 Active Job 직렬화기는 Active Job 내부에 저장됩니다:

# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer

그리고 Active Job 자체는 릴로드될 때 릴로드되지 않고, 애플리케이션 및 엔진 코드만 자동 로드 경로에서 릴로드됩니다.

MoneySerializer를 릴로드 가능하게 만드는 것은 혼란스러울 수 있습니다. 편집된 버전을 릴로드해도 Active Job에 저장된 해당 클래스 객체에는 영향을 미치지 않습니다. 실제로 Rails 7부터 MoneySerializer가 릴로드 가능하다면 이러한 초기화기에서 NameError가 발생할 수 있습니다.

다른 사용 사례는 엔진이 프레임워크 클래스를 장식하는 경우입니다:

initializer "decorate ActionController::Base" do
  ActiveSupport.on_load(:action_controller_base) do
    include MyDecoration
  end
end

여기서 초기화기가 실행될 때 MyDecoration에 저장된 모듈 객체는 ActionController::Base의 조상이 되며, MyDecoration을 릴로드하는 것은 무의미합니다. 해당 조상 체인에 영향을 미치지 않습니다.

자동 로드 한 번 경로의 클래스와 모듈은 config/initializers에서 자동 로드될 수 있습니다. 따라서 이 구성으로 다음이 작동합니다:

# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer

정보: 기술적으로 :bootstrap_hook 이후에 실행되는 모든 초기화기에서 once 자동 로더가 관리하는 클래스와 모듈을 자동 로드할 수 있습니다.

자동 로드 한 번 경로는 Rails.autoloaders.once에 의해 관리됩니다.

config.autoloadlibonce(ignore:)

config.autoload_lib_once 메서드는 config.autoload_lib와 유사하지만 libconfig.autoload_once_paths에 추가합니다. config/application.rb 또는 config/environments/*.rb에서 호출해야 하며 엔진에서는 사용할 수 없습니다.

config.autoload_lib_once를 호출하면 애플리케이션 초기화기에서도 lib의 클래스와 모듈을 자동 로드할 수 있지만 릴로드되지 않습니다.

config.autoload_lib_once는 7.1 이전에는 사용할 수 없지만, 애플리케이션이 Zeitwerk를 사용하는 한 다음과 같이 모방할 수 있습니다:

”`ruby계속해서 번역문:

config/application.rb

module MyApp class Application < Rails::Application lib = root.join(“lib”)

config.autoload_once_paths << lib
config.eager_load_paths << lib

Rails.autoloaders.once.ignore(
  lib.join("assets"),
  lib.join("tasks"),
  lib.join("generators")
)

# ...

end end “`

릴로드

Rails는 자동으로 애플리케이션 파일이 변경되면 클래스와 모듈을 릴로드합니다.

보다 구체적으로 말하면, 웹 서버가 실행 중이고 애플리케이션 파일이 수정된 경우 Rails는 다음 요청이 처리되기 전에 main 자동 로더가 관리하는 모든 자동 로드된 상수를 언로드합니다. 그렇게 하면 해당 요청 중에 사용된 애플리케이션 클래스 또는 모듈이 다시 자동 로드되어 파일 시스템의 현재 구현을 선택할 수 있습니다.

릴로드는 활성화하거나 비활성화할 수 있습니다. 이 동작을 제어하는 설정은 config.enable_reloading으로, development 모드에서는 기본적으로 true, production 모드에서는 기본적으로 false입니다. 호환성을 위해 Rails는 config.cache_classes도 지원하며, 이는 !config.enable_reloading과 동등합니다.

Rails는 기본적으로 이벤트 기반 파일 모니터를 사용하여 파일 변경을 감지합니다. 대신 자동 로드 경로를 걸어 파일 변경을 감지하도록 구성할 수 있습니다. 이는 config.file_watcher 설정에 의해 제어됩니다.

Rails 콘솔에는 config.enable_reloading 값과 관계없이 파일 감시기가 활성화되지 않습니다. 일반적으로 콘솔 세션 중간에 코드가 릴로드되면 혼란스러울 수 있기 때문입니다. 개별 요청과 마찬가지로 일반적으로 콘솔 세션에서는 일관되고 변경되지 않는 애플리케이션 클래스와 모듈 집합으로 제공되기를 원합니다.

그러나 reload!를 실행하여 콘솔에서 강제로 릴로드할 수 있습니다:

irb(main):001:0> User.object_id
=> 70136277390120
irb(main):002:0> reload!
Reloading...
=> true
irb(main):003:0> User.object_id
=> 70136284426020

볼 수 있듯이 User 상수에 저장된 클래스 객체는 릴로드 후 다릅니다.

릴로드 및 오래된 객체

Ruby에는 메모리에서 클래스와 모듈을 진정으로 릴로드하고 이를 모든 곳에 반영하는 방법이 없다는 것을 이해하는 것이 매우 중요합니다. 기술적으로 User 클래스를 "언로드"한다는 것은 Object.send(:remove_const, "User")를 통해 User 상수를 제거하는 것을 의미합니다.

예를 들어, Rails 콘솔 세션을 살펴보세요:

irb> joe = User.new
irb> reload!
irb> alice = User.new
irb> joe.class == alice.class
=> false

joe는 원래 User 클래스의 인스턴스입니다. 릴로드가 발생하면 User 상수는 새로 로드된 클래스를 평가합니다. alice는 새로 로드된 User의 인스턴스이지만 joe는 그렇지 않습니다. 그의 클래스는 오래된 것입니다. joe를 다시 정의하거나, IRB 하위 세션을 시작하거나, reload! 대신 새 콘솔을 시작할 수 있습니다.

릴로드 가능한 클래스를 릴로드되지 않는 곳에서 하위 클래스화하는 또 다른 상황이 있습니다:

# lib/vip_user.rb
class VipUser < User
end

User가 릴로드되면 VipUser는 그렇지 않으므로 VipUser의 상위 클래스는 원래 오래된 클래스 객체가 됩니다.

요약하면: 릴로드 가능한 클래스 또는 모듈을 캐시하지 마세요.

애플리케이션 부팅 시 자동 로드

부팅 중에 애플리케이션은 자동 로드 한 번 경로에서 자동 로드할 수 있습니다. 이는 once 자동 로더가 관리합니다. 위의 config.autoload_once_paths 섹션을 확인하세요.

그러나 자동 로드 경로에서 코드를 자동 로드할 수는 없습니다. 이는 config/initializers의 코드와 애플리케이션 또는 엔진 초기화기에도 적용됩니다.

왜? 초기화기는 애플리케이션이 부팅될 때 한 번만 실행됩니다. 릴로드될 때는 실행되지 않습니다. 초기화기에서 릴로드 가능한 클래스 또는 모듈을 사용하면 해당 클래스나 모듈에 대한 편집 내용이 초기 코드에 반영되지 않아 오래된 상태가 됩니다. 따라서 초기화 중에 릴로드 가능한 상수를 참조하는 것은 허용되지 않습니다.

대신 어떻게 해야 할지 살펴보겠습니다.

사용 사례 1: 부팅 중에 릴로드 가능한 코드 로드

부팅 및 각 릴로드 시 자동 로드

ApiGateway가 릴로드 가능한 클래스이고 애플리케이션이 부팅될 때 엔드포인트를 구성해야 한다고 가정해 보겠습니다:

# config/initializers/api_gateway_setup.rb
ApiGateway.endpoint = "https://example.com" # NameError

초기화기에서 릴로드 가능한 상수를 참조할 수 없으므로 to_prepare 블록으로 래핑해야 합니다. 이 블록은 부팅 시와 릴로드 후에 실행됩니다:

# config/initializers/api_gateway_setup.rb
Rails.application.config.to_prepare do
  ApiGateway.endpoint = "https://example.com" # CORRECT
end

참고: 역사적 이유로 이 콜백이 두 번 실행될 수 있습니다. 실행하는 코드는 멱등해야 합니다.

부팅 시에만 자동 로드

릴로드 가능한 클래스와 모듈은 after_initialize 블록에서도 자동 로드될 수 있습니다. 이 블록은 부팅 시에 실행되지만 릴로드 시에는 실행되지 않습니다. 예외적인 경우 이것이 원하는 것일 수 있습니다.

사전 비행 검사가 이 사용 사례의 한 예입니다:

# config/initializers/check_admin_presence.rb
Rails.application.config.after_initialize do
  unless Role.where(name: "admin").exists?
    abort "The admin role is not present, please seed the database."
  end
end

사용 사례 2: 부팅 중에 캐시된 상태로 유지되는 코드 로드

일부 구성은 클래스 또는 모듈 객체를 가져오고 릴로드되지 않는 곳에 저장합니다. 이러한 것들은 릴로드 가능해서는 안 됩니다. 그렇지 않으면 편집 내용이 해당 캐시된 오래된 객체에 반영되지 않습니다.

한 예는 미들웨어입니다:

config.middleware.use MyApp::Middleware::Foo

릴로드할 때 미들웨어 스택은 영향을 받지 않으므로 MyApp::Middleware::Foo가 릴로드 가능하다면 혼란스러울 것입니다. 구현 변경 사항은 아무런 효과가 없을 것입니다.

또 다른 예는 Active Job 직렬화기입니다:

# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer

초기화 중에 MoneySerializer가 평가되는 것은 사용자 정의 직렬화기에 푸시되고 릴로드 시에도 해당 객체가 유지됩니다.

또 다른 예는 레일타이나 엔진이 프레임워크 클래스를 모듈을 포함하여 장식하는 경우입니다. 예를 들어, turbo-rails는 이 방식으로 ActiveRecord::Base를 장식합니다:

initializer "turbo.broadcastable" do
  ActiveSupport.on_load(:active_record) do
    include Turbo::Broadcastable
  end
end

이렇게 하면 Turbo::Broadcastable 모듈 객체가 ActiveRecord::Base의 조상 체인에 추가됩니다. Turbo::Broadcastable이 릴로드되어도 변경 사항은 반영되지 않습니다. 조상 체인에는 여전히 원래 것이 있습니다.

결론: 이러한 클래스 또는 모듈은 릴로드 가능해서는 안 됩니다.

부팅 중에 이러한 클래스 또는 모듈을 참조하는 가장 쉬운 방법은 자동 로드 경로에 속하지 않는 디렉토리에 정의하는 것입니다. 예를 들어, lib은 관용적인 선택입니다. 기본적으로 자동 로드 경로에 속하지 않지만 $LOAD_PATH에 속합니다. 일반 require를 수행하여 로드하면 됩니다.

위에서 언급한 것처럼 다른 옵션은 해당 디렉토리를 자동 로드 한 번 경로에 두고 자동 로드하는 것입니다. config.autoloadoncepaths 섹션의 세부 정보를 확인하세요.

사용 사례 3: 엔진에 대한 애플리케이션 클래스 구성

엔진이 사용자를 모델링하는 릴로드 가능한 애플리케이션 클래스로 작동하고 이에 대한 구성 지점이 있다고 가정해 보겠습니다:

# config/initializers/my_engine.rb
MyEngine.configure do |config|
  config.user_model = User # NameError
end

릴로드 가능한 애플리케이션 코드와 잘 작동하려면 엔진이 대신 해당 클래스의 이름을 구성하도록 해야 합니다:

# config/initializers/my_engine.rb
MyEngine.configure do |config|
  config.user_model = "User" # OK
end

그런 다음 런타임에 config.user_model.constantize를 사용하여 현재 클래스 객체를 얻을 수 있습니다.

적극적인 로드

프로덕션 유사 환경에서는 일반적으로 애플리케이션이 부팅될 때 모든 애플리케이션 코드를 로드하는 것이 좋습니다. 적극적인 로드를 통해 모든 것을 메모리에 준비하여 즉시 요청을 처리할 수 있으며, CoW에도 친화적입니다.

적극적인 로드는 config.eager_load 플래그에 의해 제어되며, production 환경을 제외한 모든 환경에서 기본적으로 비활성화됩니다. Rake 작업이 실행될 때는 [`config.rake계속해서 번역문:

eagerload][]에 의해config.eager_load가 재정의되며, 기본적으로false`입니다. 따라서 기본적으로 프로덕션 환경에서 Rake 작업은 애플리케이션을 적극적으로 로드하지 않습니다.

파일이 적극적으로 로드되는 순서는 정의되지 않습니다.

적극적인 로드 중에 Rails는 Zeitwerk::Loader.eager_load_all을 호출합니다. 이를 통해 Zeitwerk가 관리하는 모든 젬 종속성도 적극적으로 로드됩니다.

단일 테이블 상속

단일 테이블 상속은 느린 로드와 잘 어울리지 않습니다. Active Record는 STI 계층 구조를 인식해야 올바르게 작동하지만, 느린 로드에서는 클래스가 정확히 필요할 때만 로드됩니다!

이 근본적인 불일치를 해결하려면 STI를 사전에 로드해야 합니다. 이를 달성하는 몇 가지 옵션이 있으며, 각각 다른 트레이드오프가 있습니다. 살펴보겠습니다.

옵션 1: 적극적인 로드 활성화

STI를 사전에 로드하는 가장 쉬운 방법은 적극적인 로드를 활성화하는 것입니다:

config.eager_load = true

config/environments/development.rbconfig/environments/test.rb에 설정합니다.

이것은 간단하지만, 부팅 및 릴로드 시마다 전체 애플리케이션을 적극적으로 로드하므로 비용이 많이 들 수 있습니다. 그러나 작은 애플리케이션의 경우 이 트레이드오프가 가치 있을 수 있습니다.

옵션 2: 축소된 디렉토리 사전 로드

계층 구조를 정의하는 파일을 전용 디렉토리에 저장합니다. 개념적으로도 의미가 있습니다. 이 디렉토리는 네임스페이스를 나타내는 것이 아니라 STI를 그룹화하는 것이 유일한 목적입니다:

app/models/shapes/shape.rb
app/models/shapes/circle.rb
app/models/shapes/square.rb
app/models/shapes/triangle.rb

이 예에서는 여전히 app/models/shapes/circle.rbCircle을 정의하기를 원합니다. Shapes::Circle이 아닙니다. 이것이 개인적인 선호일 수 있으며, 기존 코드베이스에서 리팩토링을 피하는 데에도 도움이 됩니다. Zeitwerk의 축소 기능을 사용하면 이를 수행할 수 있습니다:

# config/initializers/preload_stis.rb

shapes = "#{Rails.root}/app/models/shapes"
Rails.autoloaders.main.collapse(shapes) # 네임스페이스가 아닙니다.

unless Rails.application.config.eager_load
  Rails.application.config.to_prepare do
    Rails.autoloaders.main.eager_load_dir(shapes)
  end
end

이 옵션에서는 STI가 사용되지 않더라도 이러한 몇 개의 파일을 부팅 및 릴로드 시 적극적으로 로드합니다. 그러나 STI가 많지 않다면 이것이 측정 가능한 영향을 미치지 않을 것입니다.

정보: Zeitwerk::Loader#eager_load_dir 메서드는 Zeitwerk 2.6.2에 추가되었습니다. 이전 버전의 경우 app/models/shapes 디렉토리를 나열하고 require_dependency를 호출할 수 있습니다.

경고: 모델이 추가, 수정 또는 삭제되면 릴로드가 예상대로 작동합니다. 그러나 새로운 별도의 STI 계층 구조가 애플리케이션에 추가되면 초기화기를 편집하고 서버를 다시 시작해야 합니다.

옵션 3: 일반 디렉토리 사전 로드

이전 것과 유사하지만 디렉토리가 네임스페이스로 의도되었습니다. 즉, app/models/shapes/circle.rbShapes::Circle을 정의하는 것으로 예상됩니다.

이 경우 초기화기는 동일하지만 축소 구성은 없습니다:

# config/initializers/preload_stis.rb

unless Rails.application.config.eager_load
  Rails.application.config.to_prepare do
    Rails.autoloaders.main.eager_load_dir("#{Rails.root}/app/models/shapes")
  end
end

동일한 트레이드오프.

옵션 4: 데이터베이스에서 유형 사전 로드

이 옵션에서는 파일을 어떻게 구성할 필요가 없지만 데이터베이스를 쿼리합니다:

# config/initializers/preload_stis.rb

unless Rails.application.config.eager_load
  Rails.application.config.to_prepare do
    types = Shape.unscoped.select(:type).distinct.pluck(:type)
    types.compact.each(&:constantize)
  end
end

경고: 테이블에 모든 유형이 없어도 STI는 올바르게 작동하지만 subclasses 또는 descendants와 같은 메서드는 누락된 유형을 반환하지 않습니다.

경고: 모델이 추가, 수정 또는 삭제되면 릴로드가 예상대로 작동합니다. 그러나 새로운 별도의 STI 계층 구조가 애플리케이션에 추가되면 초기화기를 편집하고 서버를 다시 시작해야 합니다.

Customizing Inflections

기본적으로 Rails는 String#camelize를 사용하여 특정 파일 또는 디렉토리 이름이 어떤 상수를 정의해야 하는지 알 수 있습니다. 예를 들어, posts_controller.rbPostsController를 정의해야 합니다. 왜냐하면 "posts_controller".camelize가 반환하는 것이 그렇기 때문입니다.

특정 파일 또는 디렉토리 이름이 원하는 대로 변형되지 않을 수 있습니다. 예를 들어, html_parser.rb는 기본적으로 HtmlParser를 정의하도록 예상됩니다. 클래스를 HTMLParser로 하고 싶다면 어떻게 해야 할까요? 몇 가지 방법이 있습니다.

가장 쉬운 방법은 약어를 정의하는 것입니다:

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym "HTML"
  inflect.acronym "SSL"
end

이렇게 하면 Active Support의 전역 변형에 영향을 줍니다. 일부 애플리케이션에서는 괜찮을 수 있지만 Active Support와 독립적으로 개별 기본 이름을 변형하는 방법도 있습니다:

Rails.autoloaders.each do |autoloader|
  autoloader.inflector.inflect(
    "html_parser" => "HTMLParser",
    "ssl_error"   => "SSLError"
  )
end

이 기술은 여전히 String#camelize에 의존하는데, 이는 기본 변형기가 대체 방법으로 사용하기 때문입니다. Active Support 변형에 전혀 의존하지 않고 변형에 대한 절대적인 제어권을 가지려면 변형기를 Zeitwerk::Inflector 인스턴스로 구성하세요:

Rails.autoloaders.each do |autoloader|
  autoloader.inflector = Zeitwerk::Inflector.new
  autoloader.inflector.inflect(
    "html_parser" => "HTMLParser",
    "ssl_error"   => "SSLError"
  )
end

해당 인스턴스에 영향을 줄 수 있는 전역 구성은 없습니다. 결정적입니다.

완전한 유연성을 위해 사용자 정의 변형기를 정의할 수도 있습니다. 자세한 내용은 Zeitwerk 문서를 확인하세요.

변형 사용자 정의는 어디에 있어야 합니까?

애플리케이션이 once 자동 로더를 사용하지 않는 경우 위의 스니펫은 config/initializers에 있을 수 있습니다. 예를 들어, Active Support 사용 사례의 경우 config/initializers/inflections.rb, 다른 경우 config/initializers/zeitwerk.rb입니다.

once 자동 로더를 사용하는 애플리케이션은 이 구성을 config/application.rb의 애플리케이션 클래스 본문으로 이동하거나 로드해야 합니다. once 자동 로더는 부팅 프로세스 초기에 변형기를 사용하기 때문입니다.

사용자 정의 네임스페이스

위에서 보았듯이 자동 로드 경로는 최상위 네임스페이스: Object를 나타냅니다.

app/services를 예로 들어 보겠습니다. 이 디렉토리는 기본적으로 생성되지 않지만 존재하는 경우 Rails는 자동으로 자동 로드 경로에 추가합니다.

기본적으로 app/services/users/signup.rb 파일은 Users::Signup을 정의하도록 예상되지만, 전체 하위 트리가 Services 네임스페이스 아래에 있기를 원한다면 어떻게 해야 할까요? 기본 설정으로는 app/services/services라는 하위 디렉토리를 만들어야 합니다.

그러나 당신의 취향에 따라 app/services/users/signup.rb가 단순히 Services::Users::Signup을 정의하기를 원할 수도 있습니다.

Zeitwerk는 사용자 정의 루트 네임스페이스를 지원하여 이 사용 사례를 해결할 수 있으며, main 자동 로더를 사용자 정의하여 이를 수행할 수 있습니다:

# config/initializers/autoloading.rb

# 네임스페이스는 존재해야 합니다.
#
# 이 예에서는 모듈을 현장에 정의합니다. 다른 곳에서 정의하고 여기에서 일반 `require`로 로드할 수도 있습니다. 어쨌든 `push_dir`은 클래스 또는 모듈 객체를 예상합니다.
module Services; end

Rails.autoloaders.main.push_dir("#{Rails.root}/app/services", namespace: Services)

Rails < 7.1에서는 이 기능을 지원하지 않았지만, 다음과 같은 추가 코드를 동일한 파일에 추가하여 작동시킬 수 있습니다:

# Rails < 7.1 애플리케이션에 대한 추가 코드.
app_services_dir = "#{Rails.root}/app/services" # 문자열이어야 합니다.
ActiveSupport::Dependencies.autoload_paths.delete(app_services_dir)
Rails.application.config.watchable_dirs[app_services_dir] = [:rb]

사용자 정의 네임스페이스는 once 자동 로더에서도 지원됩니다. 그러나 이 로더는 부팅 프로세스 초기에 설정되므로 애플리케이션 초기화기에서 구성할 수 없습니다. 대신 config/application.rb에 넣으세요.

엔진과 자동 로드

엔진은 부모 애플리케이션의 컨텍스트에서 실행되며, 해당 코드는 부모 애플리케이션에 의해 자동 로드, 릴로드 및 적극적으로 로드됩니다. 애플리케이션이 zeitwerk 모드로 실행되는 경우 엔진 코드는 zeitwerk 모드에 의해 로드됩니다. 애플리케이션이 classic 계속해서 번역문:

모드로 실행되는 경우 엔진 코드는 classic 모드에 의해 로드됩니다.

Rails가 부팅될 때 엔진 디렉토리가 자동 로드 경로에 추가되며, 자동 로더의 관점에서는 애플리케이션 소스 트리에 속하는지 엔진 소스 트리에 속하는지 차이가 없습니다. 자동 로더의 주요 입력은 자동 로드 경로이며, 이것이 애플리케이션 소스 트리에 속하는지 엔진 소스 트리에 속하는지는 관련이 없습니다.

예를 들어, 이 애플리케이션은 Devise를 사용합니다:

$ bin/rails runner 'pp ActiveSupport::Dependencies.autoload_paths'
[".../app/controllers",
 ".../app/controllers/concerns",
 ".../app/helpers",
 ".../app/models",
 ".../app/models/concerns",
 ".../gems/devise-4.8.0/app/controllers",
 ".../gems/devise-4.8.0/app/helpers",
 ".../gems/devise-4.8.0/app/mailers"]

엔진이 부모 애플리케이션의 자동 로드 모드를 제어하는 경우 엔진은 일반적으로 작성할 수 있습니다.

그러나 엔진이 Rails 6 또는 Rails 6.1을 지원하고 부모 애플리케이션을 제어하지 않는 경우 classic 또는 zeitwerk 모드에서 실행될 수 있도록 준비해야 합니다. 고려해야 할 사항:

  1. classic 모드에서 어떤 상수가 어느 시점에 로드되도록 하려면 require_dependency 호출이 필요할 수 있습니다. zeitwerk에는 필요하지 않지만, 해를 끼치지 않으며 zeitwerk 모드에서도 작동합니다.

  2. classic 모드는 상수 이름에 밑줄을 사용합니다("User” -> “user.rb”), zeitwerk 모드는 파일 이름을 대문자화합니다(“user.rb” -> “User”). 대부분의 경우 일치하지만 “HTMLParser"와 같이 연속된 대문자가 있는 경우에는 그렇지 않습니다. 호환성을 위해서는 이러한 이름을 피하는 것이 가장 쉽습니다. 이 경우 "HtmlParser"를 선택하세요.

  3. classic 모드에서 app/model/concerns/foo.rb 파일은 FooConcerns::Foo를 모두 정의할 수 있습니다. zeitwerk 모드에서는 옵션이 하나뿐입니다: Foo를 정의해야 합니다. 호환성을 위해 Foo를 정의하세요.

테스트

수동 테스트

zeitwerk:check 작업은 프로젝트 트리가 예상되는 명명 규칙을 따르는지 확인하며, 수동 확인에 유용합니다. 예를 들어, classic에서 zeitwerk 모드로 마이그레이션하거나 무언가를 수정하는 경우:

$ bin/rails zeitwerk:check
Hold on, I am eager loading the application.
All is good!

애플리케이션 구성에 따라 추가 출력이 있을 수 있지만 마지막 "All is good!"이 찾는 것입니다.

자동화된 테스트

테스트 스위트에서 애플리케이션이 올바르게 적극적으로 로드되는지 확인하는 것이 좋습니다.

이를 통해 Zeitwerk 명명 규칙 준수 및 기타 가능한 오류 조건을 다룰 수 있습니다. Testing Rails Applications 가이드의 Testing Eager Loading 섹션을 확인하세요.

문제 해결

로더의 활동을 관찰하는 것이 가장 좋은 방법입니다.

가장 쉬운 방법은 config/application.rb에 다음을 포함하는 것입니다:

Rails.autoloaders.log!

프레임워크 기본값을 로드한 후 실행합니다. 그러면 표준 출력에 추적이 출력됩니다.

파일에 로깅하려면 대신 다음을 구성하세요:

Rails.autoloaders.logger = Logger.new("#{Rails.root}/log/autoloading.log")

Rails 로거를 사용할 수 있습니다. config/application.rb가 실행될 때 Rails 로거를 사용할 수 없는 경우 대신 초기화기에서 이 설정을 구성하세요:

# config/initializers/log_autoloaders.rb
Rails.autoloaders.logger = Rails.logger

Rails.autoloaders

애플리케이션을 관리하는 Zeitwerk 인스턴스는 다음에서 사용할 수 있습니다:

Rails.autoloaders.main
Rails.autoloaders.once

술어

Rails.autoloaders.zeitwerk_enabled?

은 Rails 7 애플리케이션에서도 계속 사용할 수 있으며 true를 반환합니다.