1 Introdução
Este guia documenta o carregamento automático, recarregamento e carregamento ansioso em aplicações Rails.
Em um programa Ruby comum, você carrega explicitamente os arquivos que definem as classes e módulos que deseja usar. Por exemplo, o seguinte controlador se refere a ApplicationController
e Post
, e você normalmente emite chamadas require
para eles:
# NÃO FAÇA ISSO.
require "application_controller"
require "post"
# NÃO FAÇA ISSO.
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Isso não acontece em aplicações Rails, onde as classes e módulos da aplicação estão disponíveis em todos os lugares sem chamadas require
:
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
O Rails carrega automaticamente eles para você, se necessário. Isso é possível graças a um par de carregadores Zeitwerk que o Rails configura para você, que fornecem carregamento automático, recarregamento e carregamento ansioso.
Por outro lado, esses carregadores não gerenciam mais nada. Em particular, eles não gerenciam a biblioteca padrão do Ruby, dependências de gemas, os próprios componentes do Rails ou até mesmo (por padrão) o diretório lib
da aplicação. Esse código deve ser carregado como de costume.
2 Estrutura do Projeto
Em uma aplicação Rails, os nomes dos arquivos devem corresponder às constantes que eles definem, com os diretórios atuando como namespaces.
Por exemplo, o arquivo app/helpers/users_helper.rb
deve definir UsersHelper
e o arquivo app/controllers/admin/payments_controller.rb
deve definir Admin::PaymentsController
.
Por padrão, o Rails configura o Zeitwerk para infletir os nomes dos arquivos com String#camelize
. Por exemplo, ele espera que app/controllers/users_controller.rb
defina a constante UsersController
porque é isso que "users_controller".camelize
retorna.
A seção Customizando Inflections abaixo documenta maneiras de substituir essa configuração padrão.
Por favor, verifique a documentação do Zeitwerk para mais detalhes.
3 config.autoload_paths
Nos referimos à lista de diretórios da aplicação cujo conteúdo deve ser carregado automaticamente e (opcionalmente) recarregado como caminhos de carregamento automático. Por exemplo, app/models
. Esses diretórios representam o namespace raiz: Object
.
Os caminhos de carregamento automático são chamados de diretórios raiz na documentação do Zeitwerk, mas vamos usar "caminho de carregamento automático" neste guia.
Dentro de um caminho de carregamento automático, os nomes dos arquivos devem corresponder às constantes que eles definem, como documentado aqui.
Por padrão, os caminhos de carregamento automático de uma aplicação consistem em todos os subdiretórios de app
que existem quando a aplicação é iniciada ---exceto por assets
, javascript
e views
--- além dos caminhos de carregamento automático das engines nas quais ela pode depender.
Por exemplo, se UsersHelper
for implementado em app/helpers/users_helper.rb
, o módulo pode ser carregado automaticamente, você não precisa (e não deve) escrever uma chamada require
para ele:
$ bin/rails runner 'p UsersHelper'
UsersHelper
O Rails adiciona automaticamente diretórios personalizados em app
aos caminhos de carregamento automático. Por exemplo, se sua aplicação tiver app/presenters
, você não precisa configurar nada para carregar automaticamente os presenters; isso funciona sem problemas.
A matriz de caminhos de carregamento automático padrão pode ser estendida adicionando a config.autoload_paths
, em config/application.rb
ou config/environments/*.rb
. Por exemplo:
module MyApplication
class Application < Rails::Application
config.autoload_paths << "#{root}/extras"
end
end
Além disso, as engines podem adicionar no corpo da classe da engine e em seus próprios config/environments/*.rb
.
Por favor, não altere ActiveSupport::Dependencies.autoload_paths
; a interface pública para alterar os caminhos de carregamento automático é config.autoload_paths
.
Você não pode carregar automaticamente código nos caminhos de carregamento automático enquanto a aplicação está sendo iniciada. Em particular, diretamente em config/initializers/*.rb
. Por favor, verifique Carregamento automático quando a aplicação é iniciada abaixo para maneiras válidas de fazer isso.
Os caminhos de carregamento automático são gerenciados pelo carregador automático Rails.autoloaders.main
.
4 config.autoload_lib(ignore:)
Por padrão, o diretório lib
não faz parte dos caminhos de carregamento automático de aplicações ou engines.
O método de configuração config.autoload_lib
adiciona o diretório lib
a config.autoload_paths
e config.eager_load_paths
. Ele deve ser invocado a partir de config/application.rb
ou config/environments/*.rb
, e não está disponível para engines.
Normalmente, lib
possui subdiretórios que não devem ser gerenciados pelos carregadores automáticos. Por favor, passe o nome deles em relação a lib
no argumento de palavra-chave ignore
necessário. Por exemplo:
config.autoload_lib(ignore: %w(assets tasks))
Por quê? Embora assets
e tasks
compartilhem o diretório lib
com o código regular, o conteúdo deles não deve ser carregado automaticamente ou carregado ansiosamente. Assets
e Tasks
não são namespaces Ruby lá. O mesmo vale para geradores, se você tiver algum:
ruby
config.autoload_lib(ignore: %w(assets tasks generators))
config.autoload_lib
não está disponível antes da versão 7.1, mas você ainda pode emulá-lo desde que a aplicação utilize o 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
5 config.autoload_once_paths
Você pode querer ser capaz de carregar automaticamente classes e módulos sem recarregá-los. A configuração autoload_once_paths
armazena código que pode ser carregado automaticamente, mas não será recarregado.
Por padrão, essa coleção está vazia, mas você pode estendê-la adicionando a config.autoload_once_paths
. Você pode fazer isso em config/application.rb
ou config/environments/*.rb
. Por exemplo:
module MyApplication
class Application < Rails::Application
config.autoload_once_paths << "#{root}/app/serializers"
end
end
Além disso, os engines podem adicionar no corpo da classe do engine e em seus próprios config/environments/*.rb
.
Se app/serializers
for adicionado a config.autoload_once_paths
, o Rails não considera mais isso um caminho de carregamento automático, apesar de ser um diretório personalizado em app
. Essa configuração substitui essa regra.
Isso é importante para classes e módulos que são armazenados em locais que sobrevivem às recargas, como o próprio framework Rails.
Por exemplo, os serializadores do Active Job são armazenados dentro do Active Job:
# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer
e o próprio Active Job não é recarregado quando há uma recarga, apenas o código da aplicação e dos engines nos caminhos de carregamento automático são.
Tornar MoneySerializer
recarregável seria confuso, porque recarregar uma versão editada não teria efeito sobre a classe armazenada no Active Job. Na verdade, se MoneySerializer
fosse recarregável, a partir do Rails 7, esse inicializador geraria um NameError
.
Outro caso de uso é quando os engines decoram classes do framework:
initializer "decorate ActionController::Base" do
ActiveSupport.on_load(:action_controller_base) do
include MyDecoration
end
end
Nesse caso, o objeto do módulo armazenado em MyDecoration
no momento em que o inicializador é executado se torna um ancestral de ActionController::Base
, e recarregar MyDecoration
é inútil, não afetará essa cadeia de ancestrais.
Classes e módulos dos caminhos de carregamento automático único podem ser carregados automaticamente em config/initializers
. Portanto, com essa configuração, isso funciona:
# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer
Tecnicamente, você pode carregar automaticamente classes e módulos gerenciados pelo carregador automático once
em qualquer inicializador que seja executado após :bootstrap_hook
.
Os caminhos de carregamento automático único são gerenciados por Rails.autoloaders.once
.
6 config.autoload_lib_once(ignore:)
O método config.autoload_lib_once
é semelhante a config.autoload_lib
, exceto que adiciona lib
a config.autoload_once_paths
em vez disso. Ele deve ser invocado a partir de config/application.rb
ou config/environments/*.rb
e não está disponível para engines.
Ao chamar config.autoload_lib_once
, classes e módulos em lib
podem ser carregados automaticamente, mesmo a partir de inicializadores da aplicação, mas não serão recarregados.
config.autoload_lib_once
não está disponível antes da versão 7.1, mas você ainda pode emulá-lo desde que a aplicação utilize o Zeitwerk:
# 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
7 $LOAD_PATH
Os caminhos de carregamento automático são adicionados a $LOAD_PATH
por padrão. No entanto, o Zeitwerk usa nomes de arquivo absolutos internamente, e sua aplicação não deve fazer chamadas require
para arquivos que podem ser carregados automaticamente, então esses diretórios na verdade não são necessários lá. Você pode optar por não incluí-los com essa configuração:
config.add_autoload_paths_to_load_path = false
Isso pode acelerar um pouco as chamadas require
legítimas, pois há menos pesquisas. Além disso, se sua aplicação usa o Bootsnap, isso evita que a biblioteca construa índices desnecessários, levando a um uso menor de memória.
O diretório lib
não é afetado por essa configuração, ele é sempre adicionado a $LOAD_PATH
.
8 Recarregamento
O Rails recarrega automaticamente classes e módulos se os arquivos da aplicação nos caminhos de carregamento automático forem alterados.
Mais precisamente, se o servidor web estiver em execução e os arquivos da aplicação tiverem sido modificados, o Rails descarrega todas as constantes carregadas automaticamente gerenciadas pelo carregador automático main
logo antes da próxima requisição ser processada. Dessa forma, as classes ou módulos da aplicação usados durante essa requisição serão carregados automaticamente novamente, assim, pegando sua implementação atual no sistema de arquivos.
O recarregamento pode ser ativado ou desativado. A configuração que controla esse comportamento é config.enable_reloading
, que é true
por padrão no modo development
e false
por padrão no modo production
. Para compatibilidade com versões anteriores, o Rails também suporta config.cache_classes
, que é equivalente a !config.enable_reloading
.
O Rails usa um monitor de arquivo com eventos para detectar alterações nos arquivos por padrão. Ele também pode ser configurado para detectar alterações nos arquivos percorrendo os caminhos de carregamento automático. Isso é controlado pela configuração config.file_watcher
.
Em um console do Rails, não há monitor de arquivo ativo, independentemente do valor de config.enable_reloading
. Isso ocorre porque, normalmente, seria confuso recarregar o código no meio de uma sessão do console. Semelhante a uma requisição individual, geralmente você deseja que uma sessão do console seja atendida por um conjunto consistente e imutável de classes e módulos da aplicação.
No entanto, você pode forçar uma recarga no console executando reload!
:
irb(main):001:0> User.object_id
=> 70136277390120
irb(main):002:0> reload!
Recarregando...
=> true
irb(main):003:0> User.object_id
=> 70136284426020
Como você pode ver, o objeto da classe armazenado na constante User
é diferente após a recarga.
8.1 Recarregando e Objetos Antigos
É muito importante entender que o Ruby não tem uma maneira de recarregar verdadeiramente classes e módulos na memória e fazer com que isso seja refletido em todos os lugares onde eles já são usados. Tecnicamente, "descarregar" a classe User
significa remover a constante User
via Object.send(:remove_const, "User")
.
Por exemplo, veja esta sessão do console do Rails:
irb> joe = User.new
irb> reload!
irb> alice = User.new
irb> joe.class == alice.class
=> false
joe
é uma instância da classe User
original. Quando há uma recarga, a constante User
então avalia para uma classe recarregada diferente. alice
é uma instância do novo User
carregado, mas joe
não é - sua classe está desatualizada. Você pode definir joe
novamente, iniciar uma subseção do IRB ou simplesmente iniciar um novo console em vez de chamar reload!
.
Outra situação em que você pode encontrar esse problema é ao criar subclasses de classes recarregáveis em um local que não é recarregado:
# lib/vip_user.rb
class VipUser < User
end
se User
for recarregado, uma vez que VipUser
não é, a superclasse de VipUser
será o objeto de classe original desatualizado.
Resumindo: não armazene em cache classes ou módulos recarregáveis.
9 Carregamento Automático ao Iniciar a Aplicação
Durante a inicialização, as aplicações podem carregar automaticamente a partir dos caminhos de carregamento automático uma vez, que são gerenciados pelo carregador automático once
. Por favor, verifique a seção config.autoload_once_paths
acima.
No entanto, você não pode carregar automaticamente a partir dos caminhos de carregamento automático, que são gerenciados pelo carregador automático main
. Isso se aplica ao código em config/initializers
, bem como aos inicializadores da aplicação ou dos motores.
Por quê? Os inicializadores são executados apenas uma vez, quando a aplicação é iniciada. Eles não são executados novamente nas recargas. Se um inicializador usasse uma classe ou módulo recarregável, as edições feitas neles não seriam refletidas nesse código inicial, tornando-se assim desatualizadas. Portanto, é proibido se referir a constantes recarregáveis durante a inicialização.
Vamos ver o que fazer em vez disso.
9.1 Caso de Uso 1: Durante a Inicialização, Carregue Código Recarregável
9.1.1 Carregamento Automático na Inicialização e em Cada Recarga
Vamos imaginar que ApiGateway
é uma classe recarregável e você precisa configurar seu endpoint durante a inicialização da aplicação:
# config/initializers/api_gateway_setup.rb
ApiGateway.endpoint = "https://example.com" # NameError
Os inicializadores não podem se referir a constantes recarregáveis, você precisa envolver isso em um bloco to_prepare
, que é executado na inicialização e após cada recarga:
# config/initializers/api_gateway_setup.rb
Rails.application.config.to_prepare do
ApiGateway.endpoint = "https://example.com" # CORRETO
end
NOTA: Por razões históricas, esse retorno de chamada pode ser executado duas vezes. O código que ele executa deve ser idempotente.
9.1.2 Carregamento Automático Apenas na Inicialização
Classes e módulos recarregáveis também podem ser carregados automaticamente em blocos after_initialize
. Esses blocos são executados na inicialização, mas não são executados novamente nas recargas. Em alguns casos excepcionais, isso pode ser o que você deseja.
Verificações preliminares são um caso de uso para isso:
# config/initializers/check_admin_presence.rb
Rails.application.config.after_initialize do
unless Role.where(name: "admin").exists?
abort "A função de administrador não está presente, por favor, semeie o banco de dados."
end
end
9.2 Caso de Uso 2: Durante a Inicialização, Carregue Código que Permanece em Cache
Algumas configurações recebem um objeto de classe ou módulo e o armazenam em um local que não é recarregado. É importante que esses objetos não sejam recarregáveis, porque as edições não seriam refletidas nesses objetos em cache desatualizados.
Um exemplo disso são os middlewares:
config.middleware.use MyApp::Middleware::Foo
Quando você recarrega, a pilha de middlewares não é afetada, então seria confuso que MyApp::Middleware::Foo
seja recarregável. Alterações em sua implementação não teriam efeito.
Outro exemplo são os serializadores do Active Job:
# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer
Qualquer coisa que MoneySerializer
avalie durante a inicialização é adicionada aos serializadores personalizados e esse objeto permanece lá nas recargas.
Outro exemplo são os railties ou motores que decoram classes do framework incluindo módulos. Por exemplo, o turbo-rails
decora ActiveRecord::Base
dessa maneira:
initializer "turbo.broadcastable" do
ActiveSupport.on_load(:active_record) do
include Turbo::Broadcastable
end
end
Isso adiciona um objeto de módulo à cadeia de ancestrais de ActiveRecord::Base
. Alterações em Turbo::Broadcastable
não teriam efeito se recarregadas, a cadeia de ancestrais ainda teria a original.
Corolário: Essas classes ou módulos não podem ser recarregáveis.
A maneira mais fácil de se referir a essas classes ou módulos durante a inicialização é tê-los definidos em um diretório que não pertença aos caminhos de carregamento automático. Por exemplo, lib
é uma escolha idiomática. Por padrão, ele não pertence aos caminhos de carregamento automático, mas pertence a $LOAD_PATH
. Basta fazer um require
regular para carregá-lo.
Como observado acima, outra opção é ter o diretório que os define no autoload once paths e autoload. Por favor, verifique a seção sobre config.autoload_once_paths para mais detalhes.
9.3 Caso de uso 3: Configurar Classes de Aplicação para Engines
Vamos supor que uma engine trabalhe com a classe de aplicação recarregável que modela usuários e tenha um ponto de configuração para isso:
# config/initializers/my_engine.rb
MyEngine.configure do |config|
config.user_model = User # NameError
end
Para funcionar bem com o código de aplicação recarregável, a engine precisa que as aplicações configurem o nome dessa classe:
# config/initializers/my_engine.rb
MyEngine.configure do |config|
config.user_model = "User" # OK
end
Então, em tempo de execução, config.user_model.constantize
retorna o objeto da classe atual.
10 Carregamento Antecipado
Em ambientes semelhantes à produção, geralmente é melhor carregar todo o código da aplicação quando a aplicação é iniciada. O carregamento antecipado coloca tudo na memória pronto para atender às solicitações imediatamente, e também é amigável ao CoW.
O carregamento antecipado é controlado pela flag config.eager_load
, que está desativada por padrão em todos os ambientes, exceto production
. Quando uma tarefa Rake é executada, config.eager_load
é substituída por config.rake_eager_load
, que é false
por padrão. Portanto, por padrão, em ambientes de produção, as tarefas Rake não carregam antecipadamente a aplicação.
A ordem em que os arquivos são carregados antecipadamente é indefinida.
Durante o carregamento antecipado, o Rails invoca Zeitwerk::Loader.eager_load_all
. Isso garante que todas as dependências de gem gerenciadas pelo Zeitwerk também sejam carregadas antecipadamente.
11 Herança de Tabela Única
A herança de tabela única não funciona bem com o carregamento preguiçoso: o Active Record precisa estar ciente das hierarquias de STI para funcionar corretamente, mas quando o carregamento preguiçoso, as classes são precisamente carregadas apenas sob demanda!
Para resolver essa incompatibilidade fundamental, precisamos carregar antecipadamente os STIs. Existem algumas opções para fazer isso, com diferentes compensações. Vamos vê-las.
11.1 Opção 1: Habilitar Carregamento Antecipado
A maneira mais fácil de carregar antecipadamente os STIs é habilitar o carregamento antecipado definindo:
config.eager_load = true
em config/environments/development.rb
e config/environments/test.rb
.
Isso é simples, mas pode ser custoso porque carrega antecipadamente toda a aplicação na inicialização e em cada recarregamento. No entanto, a compensação pode valer a pena para aplicações pequenas.
11.2 Opção 2: Carregar Antecipadamente um Diretório Colapsado
Armazene os arquivos que definem a hierarquia em um diretório dedicado, o que também faz sentido conceitualmente. O diretório não tem a intenção de representar um namespace, seu único propósito é agrupar o STI:
app/models/shapes/shape.rb
app/models/shapes/circle.rb
app/models/shapes/square.rb
app/models/shapes/triangle.rb
Neste exemplo, ainda queremos que app/models/shapes/circle.rb
defina Circle
, não Shapes::Circle
. Essa pode ser sua preferência pessoal para manter as coisas simples e também evita refatorações em bases de código existentes. A funcionalidade de colapsar do Zeitwerk nos permite fazer isso:
# config/initializers/preload_stis.rb
shapes = "#{Rails.root}/app/models/shapes"
Rails.autoloaders.main.collapse(shapes) # Não é um namespace.
unless Rails.application.config.eager_load
Rails.application.config.to_prepare do
Rails.autoloaders.main.eager_load_dir(shapes)
end
end
Nesta opção, carregamos antecipadamente esses poucos arquivos na inicialização e recarregamos mesmo se o STI não for usado. No entanto, a menos que sua aplicação tenha muitos STIs, isso não terá nenhum impacto mensurável.
O método Zeitwerk::Loader#eager_load_dir
foi adicionado no Zeitwerk 2.6.2. Para versões mais antigas, você ainda pode listar o diretório app/models/shapes
e chamar require_dependency
em seu conteúdo.
AVISO: Se modelos forem adicionados, modificados ou excluídos do STI, o recarregamento funcionará como esperado. No entanto, se uma nova hierarquia de STI separada for adicionada à aplicação, você precisará editar o inicializador e reiniciar o servidor.
11.3 Opção 3: Carregar Antecipadamente um Diretório Regular
Semelhante à opção anterior, mas o diretório é destinado a ser um namespace. Ou seja, espera-se que app/models/shapes/circle.rb
defina Shapes::Circle
.
Para esta opção, o inicializador é o mesmo, exceto que nenhum colapso é configurado:
# 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
Mesmas compensações.
11.4 Opção 4: Carregar Tipos do Banco de Dados
Nesta opção, não precisamos organizar os arquivos de nenhuma maneira, mas acessamos o banco de dados:
# 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
AVISO: O STI funcionará corretamente mesmo se a tabela não tiver todos os tipos, mas métodos como subclasses
ou descendants
não retornarão os tipos ausentes.
AVISO: Se modelos forem adicionados, modificados ou excluídos do STI, o recarregamento funcionará como esperado. No entanto, se uma nova hierarquia de STI separada for adicionada à aplicação, você precisará editar o inicializador e reiniciar o servidor.
12 Personalizando Inflections
Por padrão, o Rails usa String#camelize
para saber qual constante um determinado arquivo ou nome de diretório deve definir. Por exemplo, posts_controller.rb
deve definir PostsController
porque é isso que "posts_controller".camelize
retorna.
Pode ser o caso de que um determinado nome de arquivo ou diretório não seja infletido como você deseja. Por exemplo, espera-se que html_parser.rb
defina HtmlParser
por padrão. E se você preferir que a classe seja HTMLParser
? Existem algumas maneiras de personalizar isso.
A maneira mais fácil é definir acrônimos:
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym "HTML"
inflect.acronym "SSL"
end
Fazendo isso afeta como o Active Support inflete globalmente. Isso pode ser bom em algumas aplicações, mas você também pode personalizar como camelize nomes de arquivos individualmente, independentemente do Active Support, passando uma coleção de substituições para os inflectors padrão:
Rails.autoloaders.each do |autoloader|
autoloader.inflector.inflect(
"html_parser" => "HTMLParser",
"ssl_error" => "SSLError"
)
end
Essa técnica ainda depende de String#camelize
, porque é isso que os inflectors padrão usam como fallback. Se você preferir não depender das inflections do Active Support e ter controle absoluto sobre as inflections, configure os inflectors como instâncias de Zeitwerk::Inflector
:
Rails.autoloaders.each do |autoloader|
autoloader.inflector = Zeitwerk::Inflector.new
autoloader.inflector.inflect(
"html_parser" => "HTMLParser",
"ssl_error" => "SSLError"
)
end
Não há uma configuração global que possa afetar essas instâncias; elas são determinísticas.
Você pode até definir um inflector personalizado para ter total flexibilidade. Por favor, verifique a documentação do Zeitwerk para mais detalhes.
12.1 Onde Deve Ir a Personalização de Inflections?
Se uma aplicação não usa o autoloader once
, os trechos acima podem ser colocados em config/initializers
. Por exemplo, config/initializers/inflections.rb
para o caso de uso do Active Support, ou config/initializers/zeitwerk.rb
para os outros casos.
Aplicações que usam o autoloader once
precisam mover ou carregar essa configuração do corpo da classe de aplicação em config/application.rb
, porque o autoloader once
usa o inflector no início do processo de inicialização.
13 Namespaces Personalizados
Como vimos acima, os caminhos de autoload representam o namespace de nível superior: Object
.
Vamos considerar app/services
, por exemplo. Este diretório não é gerado por padrão, mas se ele existir, o Rails o adiciona automaticamente aos caminhos de autoload.
Por padrão, espera-se que o arquivo app/services/users/signup.rb
defina Users::Signup
, mas e se você preferir que toda a subárvore esteja sob um namespace Services
? Bem, com as configurações padrão, isso pode ser feito criando um subdiretório: app/services/services
.
No entanto, dependendo do seu gosto, isso pode não parecer certo para você. Você pode preferir que app/services/users/signup.rb
simplesmente defina Services::Users::Signup
.
O Zeitwerk suporta namespaces raiz personalizados para abordar esse caso de uso, e você pode personalizar o autoloader main
para conseguir isso:
# config/initializers/autoloading.rb
# O namespace precisa existir.
#
# Neste exemplo, definimos o módulo no local. Também poderia ser criado
# em outro lugar e sua definição carregada aqui com um `require` comum.
# Em qualquer caso, `push_dir` espera um objeto de classe ou módulo.
module Services; end
Rails.autoloaders.main.push_dir("#{Rails.root}/app/services", namespace: Services)
O Rails < 7.1 não suportava esse recurso, mas você ainda pode adicionar esse código adicional no mesmo arquivo e fazê-lo funcionar:
# Código adicional para aplicações em execução no Rails < 7.1.
app_services_dir = "#{Rails.root}/app/services" # precisa ser uma string
ActiveSupport::Dependencies.autoload_paths.delete(app_services_dir)
Rails.application.config.watchable_dirs[app_services_dir] = [:rb]
Namespaces personalizados também são suportados para o autoloader once
. No entanto, como esse é configurado mais cedo no processo de inicialização, a configuração não pode ser feita em um inicializador de aplicação. Em vez disso, coloque-o em config/application.rb
, por exemplo.
14 Autoload e Engines
Os engines são executados no contexto de uma aplicação pai, e seu código é carregado, recarregado e carregado antecipadamente pela aplicação pai. Se a aplicação é executada no modo zeitwerk
, o código do engine é carregado no modo zeitwerk
. Se a aplicação é executada no modo classic
, o código do engine é carregado no modo classic
.
Quando o Rails inicializa, os diretórios do engine são adicionados aos caminhos de autoload, e do ponto de vista do autoloader, não há diferença. As principais entradas dos autoloaders são os caminhos de autoload, e se eles pertencem à árvore de origem da aplicação ou a alguma árvore de origem do engine é irrelevante.
Por exemplo, esta aplicação usa o 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"]
Se o engine controla o modo de autoload de sua aplicação pai, o engine pode ser escrito normalmente.
No entanto, se um mecanismo suporta o Rails 6 ou o Rails 6.1 e não controla suas aplicações pai, ele precisa estar pronto para ser executado no modo classic
ou zeitwerk
. Coisas a serem consideradas:
Se o modo
classic
precisar de uma chamadarequire_dependency
para garantir que uma constante seja carregada em algum momento, escreva-a. Embora ozeitwerk
não precise disso, não fará mal, funcionará também no modozeitwerk
.O modo
classic
sublinha os nomes das constantes ("User" -> "user.rb"), e o modozeitwerk
cameliza os nomes dos arquivos ("user.rb" -> "User"). Eles coincidem na maioria dos casos, mas não se houver sequências de letras maiúsculas consecutivas, como em "HTMLParser". A maneira mais fácil de ser compatível é evitar esses nomes. Nesse caso, escolha "HtmlParser".No modo
classic
, o arquivoapp/model/concerns/foo.rb
pode definir tantoFoo
quantoConcerns::Foo
. No modozeitwerk
, há apenas uma opção: ele deve definirFoo
. Para ser compatível, definaFoo
.
15 Testes
15.1 Testes Manuais
A tarefa zeitwerk:check
verifica se a estrutura do projeto segue as convenções de nomenclatura esperadas e é útil para verificações manuais. Por exemplo, se você está migrando do modo classic
para o modo zeitwerk
, ou se está corrigindo algo:
% bin/rails zeitwerk:check
Aguarde, estou carregando a aplicação.
Está tudo bem!
Pode haver saída adicional dependendo da configuração da aplicação, mas o último "Está tudo bem!" é o que você está procurando.
15.2 Testes Automatizados
É uma boa prática verificar no conjunto de testes se o projeto é carregado corretamente.
Isso abrange a conformidade com a nomenclatura do Zeitwerk e outras possíveis condições de erro. Por favor, verifique a seção sobre testes de carregamento antecipado no guia Testing Rails Applications.
16 Solucionando Problemas
A melhor maneira de acompanhar o que os carregadores estão fazendo é inspecionar sua atividade.
A maneira mais fácil de fazer isso é incluir
Rails.autoloaders.log!
em config/application.rb
após carregar as configurações padrão do framework. Isso imprimirá rastreamentos na saída padrão.
Se você preferir registrar em um arquivo, configure isso em vez disso:
Rails.autoloaders.logger = Logger.new("#{Rails.root}/log/autoloading.log")
O logger do Rails ainda não está disponível quando config/application.rb
é executado. Se você preferir usar o logger do Rails, configure essa configuração em um inicializador:
# config/initializers/log_autoloaders.rb
Rails.autoloaders.logger = Rails.logger
17 Rails.autoloaders
As instâncias do Zeitwerk que gerenciam sua aplicação estão disponíveis em
Rails.autoloaders.main
Rails.autoloaders.once
O predicado
Rails.autoloaders.zeitwerk_enabled?
ainda está disponível em aplicações Rails 7 e retorna true
.
Feedback
Você é incentivado a ajudar a melhorar a qualidade deste guia.
Por favor, contribua se encontrar algum erro de digitação ou factual. Para começar, você pode ler nossa contribuição à documentação seção.
Você também pode encontrar conteúdo incompleto ou desatualizado. Por favor, adicione qualquer documentação ausente para o principal. Certifique-se de verificar Guias Edge primeiro para verificar se os problemas já foram corrigidos ou não no branch principal. Verifique as Diretrizes dos Guias do Ruby on Rails para estilo e convenções.
Se por algum motivo você encontrar algo para corrigir, mas não puder corrigi-lo você mesmo, por favor abra uma issue.
E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem-vinda no Fórum oficial do Ruby on Rails.