자동 로드 및 상수 릴로드
이 가이드는 zeitwerk
모드에서 자동 로드 및 릴로드가 작동하는 방식을 문서화합니다.
이 가이드를 읽고 나면 다음을 알 수 있습니다:
- 관련 Rails 구성
- 프로젝트 구조
- 자동 로드, 릴로드 및 적극적인 로드
- 단일 테이블 상속
- 그리고 더 많은 것들
소개
정보. 이 가이드는 Rails 애플리케이션의 자동 로드, 릴로드 및 적극적인 로드를 문서화합니다.
일반적인 Ruby 프로그램에서는 사용하려는 클래스와 모듈을 정의하는 파일을 명시적으로 로드합니다. 예를 들어, 다음 컨트롤러는 ApplicationController
와 Post
를 참조하며, 일반적으로 이에 대한 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
, javascript
및 views
는 제외)와 종속된 엔진의 자동 로드 경로로 구성됩니다.
예를 들어, UsersHelper
가 app/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_paths
및 config.eager_load_paths
에 추가합니다. config/application.rb
또는 config/environments/*.rb
에서 호출해야 하며 엔진에서는 사용할 수 없습니다.
일반적으로 lib
에는 자동 로더가 관리해서는 안 되는 하위 디렉토리가 있습니다. 필수 ignore
키워드 인수에 해당 이름을 상대 경로로 전달하세요. 예를 들어:
config.autoload_lib(ignore: %w(assets tasks))
왜? assets
와 tasks
는 일반 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/serializers
가 config.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
와 유사하지만 lib
를 config.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.rb
및 config/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.rb
가 Circle
을 정의하기를 원합니다. 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.rb
는 Shapes::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.rb
는 PostsController
를 정의해야 합니다. 왜냐하면 "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
모드에서 실행될 수 있도록 준비해야 합니다. 고려해야 할 사항:
classic
모드에서 어떤 상수가 어느 시점에 로드되도록 하려면require_dependency
호출이 필요할 수 있습니다.zeitwerk
에는 필요하지 않지만, 해를 끼치지 않으며zeitwerk
모드에서도 작동합니다.classic
모드는 상수 이름에 밑줄을 사용합니다("User” -> “user.rb”),zeitwerk
모드는 파일 이름을 대문자화합니다(“user.rb” -> “User”). 대부분의 경우 일치하지만 “HTMLParser"와 같이 연속된 대문자가 있는 경우에는 그렇지 않습니다. 호환성을 위해서는 이러한 이름을 피하는 것이 가장 쉽습니다. 이 경우 "HtmlParser"를 선택하세요.classic
모드에서app/model/concerns/foo.rb
파일은Foo
와Concerns::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
를 반환합니다.