1 Introducción
Esta guía documenta la autocarga, recarga y carga ansiosa en aplicaciones Rails.
En un programa Ruby ordinario, cargas explícitamente los archivos que definen las clases y módulos que deseas utilizar. Por ejemplo, el siguiente controlador se refiere a ApplicationController
y Post
, y normalmente emitirías llamadas require
para ellos:
# NO HAGAS ESTO.
require "application_controller"
require "post"
# NO HAGAS ESTO.
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Esto no ocurre en las aplicaciones Rails, donde las clases y módulos de la aplicación están disponibles en todas partes sin llamadas require
:
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Rails los autocarga en tu nombre si es necesario. Esto es posible gracias a un par de cargadores Zeitwerk que Rails configura en tu nombre, los cuales proporcionan autocarga, recarga y carga ansiosa.
Por otro lado, esos cargadores no administran nada más. En particular, no administran la biblioteca estándar de Ruby, las dependencias de gemas, los componentes de Rails mismos o incluso (por defecto) el directorio lib
de la aplicación. Ese código debe cargarse como de costumbre.
2 Estructura del proyecto
En una aplicación Rails, los nombres de los archivos deben coincidir con las constantes que definen, y los directorios actúan como espacios de nombres.
Por ejemplo, el archivo app/helpers/users_helper.rb
debe definir UsersHelper
y el archivo app/controllers/admin/payments_controller.rb
debe definir Admin::PaymentsController
.
Por defecto, Rails configura Zeitwerk para que infleccione los nombres de archivo con String#camelize
. Por ejemplo, espera que app/controllers/users_controller.rb
defina la constante UsersController
porque eso es lo que devuelve "users_controller".camelize
.
La sección Personalización de las inflexiones a continuación documenta formas de anular esta configuración predeterminada.
Por favor, consulta la documentación de Zeitwerk para más detalles.
3 config.autoload_paths
Nos referimos a la lista de directorios de la aplicación cuyo contenido se debe autocargar y (opcionalmente) recargar como rutas de autocarga. Por ejemplo, app/models
. Tales directorios representan el espacio de nombres raíz: Object
.
Las rutas de autocarga se llaman directorios raíz en la documentación de Zeitwerk, pero nos quedaremos con "ruta de autocarga" en esta guía.
Dentro de una ruta de autocarga, los nombres de archivo deben coincidir con las constantes que definen, como se documenta aquí.
Por defecto, las rutas de autocarga de una aplicación consisten en todos los subdirectorios de app
que existen cuando la aplicación se inicia, excepto assets
, javascript
y views
, además de las rutas de autocarga de las engines en las que pueda depender.
Por ejemplo, si UsersHelper
se implementa en app/helpers/users_helper.rb
, el módulo se puede autocargar, no necesitas (y no debes escribir) una llamada require
para ello:
$ bin/rails runner 'p UsersHelper'
UsersHelper
Rails agrega automáticamente directorios personalizados bajo app
a las rutas de autocarga. Por ejemplo, si tu aplicación tiene app/presenters
, no necesitas configurar nada para autocargar los presentadores; funciona de forma predeterminada.
El array de rutas de autocarga predeterminadas se puede ampliar agregando a config.autoload_paths
, en config/application.rb
o config/environments/*.rb
. Por ejemplo:
module MyApplication
class Application < Rails::Application
config.autoload_paths << "#{root}/extras"
end
end
Además, las engines pueden agregar rutas de autocarga en el cuerpo de la clase de la engine y en sus propios config/environments/*.rb
.
ADVERTENCIA. Por favor, no mutar ActiveSupport::Dependencies.autoload_paths
; la interfaz pública para cambiar las rutas de autocarga es config.autoload_paths
.
ADVERTENCIA: No puedes autocargar código en las rutas de autocarga mientras la aplicación se inicia. En particular, directamente en config/initializers/*.rb
. Por favor, consulta Autocarga cuando la aplicación se inicia más abajo para conocer las formas válidas de hacerlo.
Las rutas de autocarga son gestionadas por el cargador automático Rails.autoloaders.main
.
4 config.autoload_lib(ignore:)
Por defecto, el directorio lib
no pertenece a las rutas de autocarga de las aplicaciones o engines.
El método de configuración config.autoload_lib
agrega el directorio lib
a config.autoload_paths
y config.eager_load_paths
. Debe invocarse desde config/application.rb
o config/environments/*.rb
, y no está disponible para las engines.
Normalmente, lib
tiene subdirectorios que no deben ser gestionados por los cargadores automáticos. Por favor, pasa su nombre relativo a lib
en el argumento de palabra clave ignore
requerido. Por ejemplo:
config.autoload_lib(ignore: %w(assets tasks))
¿Por qué? Mientras que assets
y tasks
comparten el directorio lib
con el código regular, su contenido no está destinado a ser autocargado o cargado ansiosamente. Assets
y Tasks
no son espacios de nombres de Ruby allí. Lo mismo ocurre con los generadores si tienes alguno:
ruby
config.autoload_lib(ignore: %w(assets tasks generators))
config.autoload_lib
no está disponible antes de la versión 7.1, pero aún puedes emularlo siempre y cuando la aplicación use 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
Es posible que desees poder cargar clases y módulos sin volver a cargarlos. La configuración autoload_once_paths
almacena código que se puede cargar automáticamente, pero no se volverá a cargar.
De forma predeterminada, esta colección está vacía, pero puedes ampliarla agregando elementos a config.autoload_once_paths
. Puedes hacerlo en config/application.rb
o config/environments/*.rb
. Por ejemplo:
module MyApplication
class Application < Rails::Application
config.autoload_once_paths << "#{root}/app/serializers"
end
end
Además, los motores pueden agregar elementos en el cuerpo de la clase del motor y en sus propios config/environments/*.rb
.
Si se agrega app/serializers
a config.autoload_once_paths
, Rails ya no considera esto como una ruta de carga automática, a pesar de ser un directorio personalizado dentro de app
. Esta configuración anula esa regla.
Esto es importante para clases y módulos que se almacenan en lugares que sobreviven a las recargas, como el propio framework de Rails.
Por ejemplo, los serializadores de Active Job se almacenan dentro de Active Job:
# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer
y Active Job en sí no se recarga cuando hay una recarga, solo se recarga el código de la aplicación y los motores en las rutas de carga automática.
Hacer que MoneySerializer
sea recargable sería confuso, porque volver a cargar una versión editada no tendría efecto en ese objeto de clase almacenado en Active Job. De hecho, si MoneySerializer
fuera recargable, a partir de Rails 7, dicho inicializador generaría un NameError
.
Otro caso de uso es cuando los motores decoran clases del framework:
initializer "decorate ActionController::Base" do
ActiveSupport.on_load(:action_controller_base) do
include MyDecoration
end
end
Allí, el objeto de módulo almacenado en MyDecoration
en el momento en que se ejecuta el inicializador se convierte en un ancestro de ActionController::Base
, y volver a cargar MyDecoration
no tiene sentido, no afectará esa cadena de ancestros.
Las clases y módulos de las rutas de carga automática única se pueden cargar automáticamente en config/initializers
. Por lo tanto, con esa configuración, esto funciona:
# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer
Técnicamente, se pueden cargar automáticamente clases y módulos gestionados por el cargador automático once
en cualquier inicializador que se ejecute después de :bootstrap_hook
.
Las rutas de carga automática única son gestionadas por Rails.autoloaders.once
.
6 config.autoload_lib_once(ignore:)
El método config.autoload_lib_once
es similar a config.autoload_lib
, excepto que agrega lib
a config.autoload_once_paths
en su lugar. Debe invocarse desde config/application.rb
o config/environments/*.rb
, y no está disponible para los motores.
Al llamar a config.autoload_lib_once
, las clases y módulos en lib
se pueden cargar automáticamente, incluso desde los inicializadores de la aplicación, pero no se volverán a cargar.
config.autoload_lib_once
no está disponible antes de la versión 7.1, pero aún puedes emularlo siempre y cuando la aplicación use 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
Las rutas de carga automática se agregan a $LOAD_PATH
de forma predeterminada. Sin embargo, Zeitwerk utiliza nombres de archivo absolutos internamente y tu aplicación no debe emitir llamadas require
para archivos que se pueden cargar automáticamente, por lo que esos directorios no son necesarios allí. Puedes optar por no incluirlos con esta configuración:
config.add_autoload_paths_to_load_path = false
Esto puede acelerar un poco las llamadas legítimas a require
, ya que hay menos búsquedas. Además, si tu aplicación utiliza Bootsnap, esto evita que la biblioteca construya índices innecesarios, lo que lleva a un menor uso de memoria.
El directorio lib
no se ve afectado por esta configuración, siempre se agrega a $LOAD_PATH
.
8 Recarga
Rails recarga automáticamente clases y módulos si los archivos de la aplicación en las rutas de carga automática cambian.
Más precisamente, si el servidor web está en ejecución y los archivos de la aplicación han sido modificados, Rails descarga todas las constantes cargadas automáticamente gestionadas por el cargador automático main
justo antes de procesar la siguiente solicitud. De esta manera, las clases o módulos de la aplicación utilizados durante esa solicitud se cargarán automáticamente nuevamente, lo que les permitirá utilizar su implementación actual en el sistema de archivos.
La recarga se puede habilitar o deshabilitar. La configuración que controla este comportamiento es config.enable_reloading
, que es true
de forma predeterminada en el modo development
y false
de forma predeterminada en el modo production
. Por razones de compatibilidad con versiones anteriores, Rails también admite config.cache_classes
, que es equivalente a !config.enable_reloading
.
Rails utiliza un monitor de archivos basado en eventos para detectar cambios en los archivos de forma predeterminada. También se puede configurar para detectar cambios en los archivos recorriendo las rutas de carga automática. Esto se controla mediante la configuración config.file_watcher
.
En una consola de Rails, no hay un monitor de archivos activo independientemente del valor de config.enable_reloading
. Esto se debe a que, normalmente, sería confuso volver a cargar el código en medio de una sesión de consola. Al igual que en una solicitud individual, generalmente deseas que una sesión de consola se sirva con un conjunto coherente y no cambiante de clases y módulos de la aplicación.
Sin embargo, puedes forzar una recarga en la consola ejecutando reload!
:
irb(main):001:0> User.object_id
=> 70136277390120
irb(main):002:0> reload!
Recargando...
=> true
irb(main):003:0> User.object_id
=> 70136284426020
Como puedes ver, el objeto de clase almacenado en la constante User
es diferente después de la recarga.
8.1 Recarga y objetos obsoletos
Es muy importante entender que Ruby no tiene una forma de recargar verdaderamente clases y módulos en memoria, y que esto se refleje en todos los lugares donde ya se están utilizando. Técnicamente, "descargar" la clase User
significa eliminar la constante User
a través de Object.send(:remove_const, "User")
.
Por ejemplo, echa un vistazo a esta sesión de la consola de Rails:
irb> joe = User.new
irb> reload!
irb> alice = User.new
irb> joe.class == alice.class
=> false
joe
es una instancia de la clase User
original. Cuando hay una recarga, la constante User
se evalúa como una clase diferente y recargada. alice
es una instancia del nuevo User
cargado, pero joe
no lo es: su clase está obsoleta. Puedes definir joe
nuevamente, iniciar una sub-sesión de IRB o simplemente abrir una nueva consola en lugar de llamar a reload!
.
Otra situación en la que puedes encontrar este problema es al heredar de clases recargables en un lugar que no se recarga:
# lib/vip_user.rb
class VipUser < User
end
Si se recarga User
, ya que VipUser
no lo hace, la superclase de VipUser
será el objeto de clase original y obsoleto.
En resumen: no almacenes en caché clases o módulos recargables.
9 Carga automática al iniciar la aplicación
Durante el inicio, las aplicaciones pueden cargar automáticamente desde las rutas de carga automática una vez, que son gestionadas por el cargador automático once
. Por favor, consulta la sección config.autoload_once_paths
anterior.
Sin embargo, no puedes cargar automáticamente desde las rutas de carga automática, que son gestionadas por el cargador automático main
. Esto se aplica al código en config/initializers
así como a los inicializadores de la aplicación o motores.
¿Por qué? Los inicializadores solo se ejecutan una vez, cuando se inicia la aplicación. No se ejecutan de nuevo en las recargas. Si un inicializador utiliza una clase o módulo recargable, las ediciones en ellos no se reflejarían en ese código inicial, volviéndose obsoletas. Por lo tanto, no se permite hacer referencia a constantes recargables durante la inicialización.
Veamos qué hacer en su lugar.
9.1 Caso de uso 1: Durante el inicio, cargar código recargable
9.1.1 Carga automática en el inicio y en cada recarga
Imaginemos que ApiGateway
es una clase recargable y necesitas configurar su punto final mientras se inicia la aplicación:
# config/initializers/api_gateway_setup.rb
ApiGateway.endpoint = "https://example.com" # NameError
Los inicializadores no pueden hacer referencia a constantes recargables, debes envolver eso en un bloque to_prepare
, que se ejecuta en el inicio y después de cada recarga:
# config/initializers/api_gateway_setup.rb
Rails.application.config.to_prepare do
ApiGateway.endpoint = "https://example.com" # CORRECTO
end
NOTA: Por razones históricas, esta devolución de llamada puede ejecutarse dos veces. El código que ejecuta debe ser idempotente.
9.1.2 Carga automática solo en el inicio
Las clases y módulos recargables también se pueden cargar automáticamente en bloques after_initialize
. Estos se ejecutan en el inicio, pero no se ejecutan de nuevo en las recargas. En algunos casos excepcionales, esto puede ser lo que deseas.
Los controles previos al vuelo son un caso de uso para esto:
# config/initializers/check_admin_presence.rb
Rails.application.config.after_initialize do
unless Role.where(name: "admin").exists?
abort "El rol de administrador no está presente, por favor, carga la base de datos."
end
end
9.2 Caso de uso 2: Durante el inicio, cargar código que permanece en caché
Algunas configuraciones toman un objeto de clase o módulo y lo almacenan en un lugar que no se recarga. Es importante que estos no sean recargables, porque las ediciones no se reflejarían en esos objetos en caché y obsoletos.
Un ejemplo es el middleware:
config.middleware.use MyApp::Middleware::Foo
Cuando recargas, la pila de middleware no se ve afectada, por lo que sería confuso que MyApp::Middleware::Foo
sea recargable. Los cambios en su implementación no tendrían efecto.
Otro ejemplo son los serializadores de Active Job:
# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer
Cualquier cosa a la que MoneySerializer
se evalúe durante la inicialización se añade a los serializadores personalizados, y ese objeto se mantiene allí en las recargas.
Otro ejemplo son las railties o los motores que decoran las clases del framework incluyendo módulos. Por ejemplo, turbo-rails
decora ActiveRecord::Base
de esta manera:
initializer "turbo.broadcastable" do
ActiveSupport.on_load(:active_record) do
include Turbo::Broadcastable
end
end
Eso añade un objeto de módulo a la cadena de ancestros de ActiveRecord::Base
. Los cambios en Turbo::Broadcastable
no tendrían efecto si se recargan, la cadena de ancestros seguiría teniendo el original.
Corolario: Esas clases o módulos no pueden ser recargables.
La forma más sencilla de hacer referencia a esas clases o módulos durante el inicio es tenerlos definidos en un directorio que no pertenezca a las rutas de carga automática. Por ejemplo, lib
es una elección idiomática. Por defecto, no pertenece a las rutas de carga automática, pero sí pertenece a $LOAD_PATH
. Solo realiza un require
normal para cargarlo.
Como se mencionó anteriormente, otra opción es tener el directorio que los define en la carga automática una vez y en las rutas de carga automática. Por favor, consulte la sección sobre config.autoload_once_paths para más detalles.
9.3 Caso de uso 3: Configurar clases de aplicación para motores
Supongamos que un motor funciona con la clase de aplicación recargable que modela a los usuarios, y tiene un punto de configuración para ello:
# config/initializers/my_engine.rb
MyEngine.configure do |config|
config.user_model = User # NameError
end
Para que funcione correctamente con el código de aplicación recargable, el motor necesita que las aplicaciones configuren el nombre de esa clase:
# config/initializers/my_engine.rb
MyEngine.configure do |config|
config.user_model = "User" # OK
end
Luego, en tiempo de ejecución, config.user_model.constantize
te da el objeto de clase actual.
10 Carga anticipada
En entornos similares a producción, generalmente es mejor cargar todo el código de la aplicación cuando se inicia la aplicación. La carga anticipada pone todo en memoria listo para atender las solicitudes de inmediato, y también es compatible con CoW.
La carga anticipada está controlada por la bandera config.eager_load
, que está desactivada de forma predeterminada en todos los entornos excepto production
. Cuando se ejecuta una tarea de Rake, config.eager_load
es anulado por config.rake_eager_load
, que es false
de forma predeterminada. Por lo tanto, de forma predeterminada, en entornos de producción las tareas de Rake no cargan anticipadamente la aplicación.
El orden en el que se carga anticipadamente los archivos no está definido.
Durante la carga anticipada, Rails invoca Zeitwerk::Loader.eager_load_all
. Esto asegura que todas las dependencias de gemas gestionadas por Zeitwerk también se carguen anticipadamente.
11 Herencia de tabla única
La herencia de tabla única no funciona bien con la carga perezosa: Active Record debe ser consciente de las jerarquías de STI para funcionar correctamente, pero cuando se carga perezosamente, ¡las clases se cargan precisamente solo cuando se solicitan!
Para abordar esta incompatibilidad fundamental, necesitamos precargar STIs. Hay algunas opciones para lograr esto, con diferentes compensaciones. Veámoslas.
11.1 Opción 1: Habilitar la carga anticipada
La forma más fácil de precargar STIs es habilitar la carga anticipada configurando:
config.eager_load = true
en config/environments/development.rb
y config/environments/test.rb
.
Esto es simple, pero puede ser costoso porque carga anticipadamente toda la aplicación al iniciarla y en cada recarga. Sin embargo, la compensación puede valer la pena para aplicaciones pequeñas.
11.2 Opción 2: Precargar un directorio colapsado
Almacena los archivos que definen la jerarquía en un directorio dedicado, lo cual también tiene sentido conceptualmente. El directorio no pretende representar un espacio de nombres, su único propósito es agrupar el STI:
app/models/shapes/shape.rb
app/models/shapes/circle.rb
app/models/shapes/square.rb
app/models/shapes/triangle.rb
En este ejemplo, aún queremos que app/models/shapes/circle.rb
defina Circle
, no Shapes::Circle
. Esto puede ser una preferencia personal para mantener las cosas simples y también evita refactorizaciones en bases de código existentes. La función de colapsar de Zeitwerk nos permite hacer eso:
# config/initializers/preload_stis.rb
shapes = "#{Rails.root}/app/models/shapes"
Rails.autoloaders.main.collapse(shapes) # No es un espacio de nombres.
unless Rails.application.config.eager_load
Rails.application.config.to_prepare do
Rails.autoloaders.main.eager_load_dir(shapes)
end
end
En esta opción, cargamos anticipadamente estos pocos archivos al iniciar y recargar incluso si el STI no se utiliza. Sin embargo, a menos que tu aplicación tenga muchos STIs, esto no tendrá ningún impacto medible.
El método Zeitwerk::Loader#eager_load_dir
se agregó en Zeitwerk 2.6.2. Para versiones anteriores, aún puedes listar el directorio app/models/shapes
e invocar require_dependency
en su contenido.
ADVERTENCIA: Si se agregan, modifican o eliminan modelos del STI, la recarga funciona como se espera. Sin embargo, si se agrega una nueva jerarquía de STI separada a la aplicación, deberás editar el inicializador y reiniciar el servidor.
11.3 Opción 3: Precargar un directorio regular
Similar a la anterior, pero el directorio está destinado a ser un espacio de nombres. Es decir, se espera que app/models/shapes/circle.rb
defina Shapes::Circle
.
Para esta opción, el inicializador es el mismo excepto que no se configura el colapso:
# 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
Mismas compensaciones.
11.4 Opción 4: Precargar tipos desde la base de datos
En esta opción no necesitamos organizar los archivos de ninguna manera, pero accedemos a la base de datos:
# 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
ADVERTENCIA: El STI funcionará correctamente incluso si la tabla no tiene todos los tipos, pero los métodos como subclasses
o descendants
no devolverán los tipos faltantes.
ADVERTENCIA: Si se agregan, modifican o eliminan modelos del STI, la recarga funciona como se espera. Sin embargo, si se agrega una nueva jerarquía de STI separada a la aplicación, deberás editar el inicializador y reiniciar el servidor.
12 Personalización de Inflecciones
Por defecto, Rails utiliza String#camelize
para saber qué constante debe definir un determinado archivo o nombre de directorio. Por ejemplo, posts_controller.rb
debe definir PostsController
porque eso es lo que devuelve "posts_controller".camelize
.
Puede suceder que algún nombre de archivo o directorio en particular no se infleccione como desee. Por ejemplo, se espera que html_parser.rb
defina HtmlParser
de forma predeterminada. ¿Qué pasa si prefiere que la clase sea HTMLParser
? Hay algunas formas de personalizar esto.
La forma más sencilla es definir acrónimos:
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym "HTML"
inflect.acronym "SSL"
end
Hacer esto afecta cómo Active Support infleccione globalmente. Eso puede ser válido en algunas aplicaciones, pero también puede personalizar cómo se infleccione cada nombre de archivo de forma independiente de Active Support pasando una colección de anulaciones a los inflectores predeterminados:
Rails.autoloaders.each do |autoloader|
autoloader.inflector.inflect(
"html_parser" => "HTMLParser",
"ssl_error" => "SSLError"
)
end
Sin embargo, esta técnica aún depende de String#camelize
, ya que eso es lo que los inflectores predeterminados utilizan como alternativa. Si prefiere no depender en absoluto de las inflecciones de Active Support y tener un control absoluto sobre las inflecciones, configure los inflectores como instancias de Zeitwerk::Inflector
:
Rails.autoloaders.each do |autoloader|
autoloader.inflector = Zeitwerk::Inflector.new
autoloader.inflector.inflect(
"html_parser" => "HTMLParser",
"ssl_error" => "SSLError"
)
end
No hay una configuración global que pueda afectar a dichas instancias; son deterministas.
Incluso puede definir un inflector personalizado para tener una flexibilidad total. Consulte la documentación de Zeitwerk para obtener más detalles.
12.1 ¿Dónde debe ir la personalización de inflecciones?
Si una aplicación no utiliza el cargador once
, los fragmentos anteriores se pueden colocar en config/initializers
. Por ejemplo, config/initializers/inflections.rb
para el caso de uso de Active Support, o config/initializers/zeitwerk.rb
para los demás casos.
Las aplicaciones que utilizan el cargador once
deben mover o cargar esta configuración desde el cuerpo de la clase de la aplicación en config/application.rb
, porque el cargador once
utiliza el inflector al principio del proceso de inicio.
13 Espacios de nombres personalizados
Como vimos anteriormente, las rutas de carga automática representan el espacio de nombres de nivel superior: Object
.
Consideremos app/services
, por ejemplo. Este directorio no se genera de forma predeterminada, pero si existe, Rails lo agrega automáticamente a las rutas de carga automática.
De forma predeterminada, se espera que el archivo app/services/users/signup.rb
defina Users::Signup
, pero ¿qué pasa si prefiere que todo ese subárbol esté bajo un espacio de nombres Services
? Bueno, con la configuración predeterminada, eso se puede lograr creando un subdirectorio: app/services/services
.
Sin embargo, dependiendo de sus preferencias, eso simplemente podría no parecerle correcto. Es posible que prefiera que app/services/users/signup.rb
simplemente defina Services::Users::Signup
.
Zeitwerk admite espacios de nombres raíz personalizados para abordar este caso de uso, y puede personalizar el cargador main
para lograrlo:
# config/initializers/autoloading.rb
# El espacio de nombres debe existir.
#
# En este ejemplo, definimos el módulo en el acto. También podría crearse
# en otro lugar y su definición cargarse aquí con un `require` ordinario.
# En cualquier caso, `push_dir` espera un objeto de clase o módulo.
module Services; end
Rails.autoloaders.main.push_dir("#{Rails.root}/app/services", namespace: Services)
Rails < 7.1 no admitía esta función, pero aún puede agregar este código adicional en el mismo archivo y hacerlo funcionar:
# Código adicional para aplicaciones que se ejecutan en Rails < 7.1.
app_services_dir = "#{Rails.root}/app/services" # debe ser una cadena
ActiveSupport::Dependencies.autoload_paths.delete(app_services_dir)
Rails.application.config.watchable_dirs[app_services_dir] = [:rb]
Los espacios de nombres personalizados también son compatibles con el cargador once
. Sin embargo, dado que este se configura antes en el proceso de inicio, la configuración no se puede realizar en un inicializador de la aplicación. En su lugar, colóquelo en config/application.rb
, por ejemplo.
14 Carga automática y motores
Los motores se ejecutan en el contexto de una aplicación principal y su código se carga automáticamente, se recarga y se carga de forma ansiosa por la aplicación principal. Si la aplicación se ejecuta en modo zeitwerk
, el código del motor se carga en modo zeitwerk
. Si la aplicación se ejecuta en modo classic
, el código del motor se carga en modo classic
.
Cuando Rails se inicia, los directorios del motor se agregan a las rutas de carga automática y, desde el punto de vista del cargador automático, no hay diferencia. Las entradas principales de los cargadores automáticos son las rutas de carga automática, y si pertenecen al árbol de origen de la aplicación o a algún árbol de origen del motor es irrelevante.
Por ejemplo, esta aplicación utiliza 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"]
Si el motor controla el modo de carga automática de su aplicación principal, el motor se puede escribir como de costumbre.
Sin embargo, si un motor es compatible con Rails 6 o Rails 6.1 y no controla sus aplicaciones principales, debe estar listo para ejecutarse en modo classic
o zeitwerk
. Cosas a tener en cuenta:
Si el modo
classic
necesita una llamadarequire_dependency
para asegurarse de que alguna constante se cargue en algún momento, escríbala. Si bienzeitwerk
no lo necesita, no afectará y funcionará en modozeitwerk
también.El modo
classic
subraya los nombres de las constantes ("User" -> "user.rb"), y el modozeitwerk
capitaliza los nombres de los archivos ("user.rb" -> "User"). Coinciden en la mayoría de los casos, pero no si hay series de letras mayúsculas consecutivas como en "HTMLParser". La forma más fácil de ser compatible es evitar tales nombres. En este caso, elija "HtmlParser".En el modo
classic
, el archivoapp/model/concerns/foo.rb
puede definir tantoFoo
comoConcerns::Foo
. En el modozeitwerk
, solo hay una opción: debe definirFoo
. Para ser compatible, definaFoo
.
15 Pruebas
15.1 Pruebas manuales
La tarea zeitwerk:check
verifica si el árbol del proyecto sigue las convenciones de nomenclatura esperadas y es útil para realizar comprobaciones manuales. Por ejemplo, si está migrando del modo classic
al modo zeitwerk
, o si está corrigiendo algo:
% bin/rails zeitwerk:check
Espera, estoy cargando la aplicación.
¡Todo está bien!
Puede haber una salida adicional dependiendo de la configuración de la aplicación, pero el último "¡Todo está bien!" es lo que estás buscando.
15.2 Pruebas automatizadas
Es una buena práctica verificar en el conjunto de pruebas que la carga anticipada del proyecto se realice correctamente.
Esto cubre el cumplimiento de la nomenclatura de Zeitwerk y otras posibles condiciones de error. Consulte la sección sobre pruebas de carga anticipada en la guía Testing Rails Applications.
16 Solución de problemas
La mejor manera de seguir lo que hacen los cargadores es inspeccionar su actividad.
La forma más fácil de hacerlo es incluir
Rails.autoloaders.log!
en config/application.rb
después de cargar las configuraciones predeterminadas del framework. Esto imprimirá trazas en la salida estándar.
Si prefiere registrar en un archivo, configure esto en su lugar:
Rails.autoloaders.logger = Logger.new("#{Rails.root}/log/autoloading.log")
El registrador de Rails aún no está disponible cuando se ejecuta config/application.rb
. Si prefiere usar el registrador de Rails, configure esta configuración en un inicializador en su lugar:
# config/initializers/log_autoloaders.rb
Rails.autoloaders.logger = Rails.logger
17 Rails.autoloaders
Las instancias de Zeitwerk que gestionan su aplicación están disponibles en
Rails.autoloaders.main
Rails.autoloaders.once
El predicado
Rails.autoloaders.zeitwerk_enabled?
aún está disponible en aplicaciones Rails 7 y devuelve true
.
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor, contribuye si encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.
También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.
Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.
Y por último, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.