NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN https://guides.rubyonrails.org.

Autocarga y Recarga de Constantes

Esta guía documenta cómo funciona la autocarga y recarga en modo zeitwerk.

Después de leer esta guía, sabrás:


1 Introducción

Esta guía documenta la autocarga, recarga y carga anticipada en aplicaciones Rails.

En un programa Ruby ordinario, cargas explícitamente los archivos que definen las clases y módulos que deseas usar. 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

Este no es el caso 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 anticipada.

Por otro lado, esos cargadores no gestionan nada más. En particular, no gestionan la biblioteca estándar de Ruby, las dependencias de gemas, los componentes de Rails, ni siquiera (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 archivo deben coincidir con las constantes que definen, con los directorios actuando como espacios de nombres.

Por ejemplo, el archivo app/helpers/users_helper.rb debería definir UsersHelper y el archivo app/controllers/admin/payments_controller.rb debería definir Admin::PaymentsController.

Por defecto, Rails configura Zeitwerk para inflexionar 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 "users_controller".camelize devuelve.

La sección Personalización de Inflexiones a continuación documenta formas de anular este comportamiento predeterminado.

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 debe ser autocargado y (opcionalmente) recargado como rutas de autocarga. Por ejemplo, app/models. Dichos 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 usaremos "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 los motores de los que pueda depender.

Por ejemplo, si UsersHelper se implementa en app/helpers/users_helper.rb, el módulo es autocargable, no necesitas (y no deberías escribir) una llamada require para él:

$ bin/rails runner 'p UsersHelper'
UsersHelper

Rails agrega directorios personalizados bajo app a las rutas de autocarga automáticamente. Por ejemplo, si tu aplicación tiene app/presenters, no necesitas configurar nada para autocargar presentadores; funciona automáticamente.

El array de rutas de autocarga predeterminadas puede extenderse 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, los motores pueden agregar en el cuerpo de la clase del motor y en sus propios config/environments/*.rb.

ADVERTENCIA. Por favor, no muten 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 Autoloading when the application boots más abajo para formas válidas de hacerlo.

Las rutas de autocarga son gestionadas por el autocargador Rails.autoloaders.main.

4 config.autoload_lib(ignore:)

Por defecto, el directorio lib no pertenece a las rutas de autocarga de aplicaciones o motores.

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 motores.

Normalmente, lib tiene subdirectorios que no deberían ser gestionados por los autocargadores. Por favor, pasa su nombre relativo a lib en el argumento requerido ignore. Por ejemplo:

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

¿Por qué? Mientras que assets y tasks comparten el directorio lib con código Ruby regular, su contenido no está destinado a ser recargado o cargado anticipadamente.

La lista ignore debe tener todos los subdirectorios de lib que no contienen archivos con extensión .rb, o que no deberían ser recargados o cargados anticipadamente. Por ejemplo,

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

config.autoload_lib no está disponible antes de la versión 7.1, pero aún puedes emularlo siempre que 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

Puede que desees poder autocargar clases y módulos sin recargarlos. La configuración autoload_once_paths almacena código que puede ser autocargado, pero no será recargado.

Por defecto, esta colección está vacía, pero puedes extenderla agregando 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 en el cuerpo de la clase del motor y en sus propios config/environments/*.rb.

Si app/serializers se agrega a config.autoload_once_paths, Rails ya no considera esto como una ruta de autocarga, a pesar de ser un directorio personalizado bajo app. Esta configuración anula esa regla.

Esto es clave para clases y módulos que están en caché en lugares que sobreviven a las recargas, como el propio marco 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 el código de la aplicación y motores en las rutas de autocarga lo es.

Hacer que MoneySerializer sea recargable sería confuso, porque recargar 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 lanzaría un NameError.

Otro caso de uso es cuando los motores decoran clases del marco:

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

Allí, el objeto del módulo almacenado en MyDecoration en el momento en que se ejecuta el inicializador se convierte en un ancestro de ActionController::Base, y recargar MyDecoration es inútil, no afectará esa cadena de ancestros.

Las clases y módulos de las rutas de autocarga única pueden ser autocargados en config/initializers. Así que, con esa configuración esto funciona:

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

Técnicamente, puedes autocargar clases y módulos gestionados por el autocargador once en cualquier inicializador que se ejecute después de :bootstrap_hook.

Las rutas de autocarga ú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 motores.

Al llamar a config.autoload_lib_once, las clases y módulos en lib pueden ser autocargados, incluso desde inicializadores de la aplicación, pero no serán recargados.

config.autoload_lib_once no está disponible antes de la versión 7.1, pero aún puedes emularlo siempre que 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 Recarga

Rails recarga automáticamente las clases y módulos si cambian los archivos de la aplicación en las rutas de autocarga.

Más precisamente, si el servidor web está ejecutándose y los archivos de la aplicación han sido modificados, Rails descarga todas las constantes autocargadas gestionadas por el autocargador main justo antes de que se procese la siguiente solicitud. De esa manera, las clases o módulos de la aplicación utilizados durante esa solicitud serán autocargados nuevamente, recogiendo así su implementación actual en el sistema de archivos.

La recarga puede habilitarse o deshabilitarse. La configuración que controla este comportamiento es config.enable_reloading, que es true por defecto en modo development, y false por defecto en modo production. Por 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 por defecto. Puede configurarse en su lugar para detectar cambios de archivos recorriendo las rutas de autocarga. 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 tener código recargado en medio de una sesión de consola. Similar a una solicitud individual, generalmente deseas que una sesión de consola sea atendida por un conjunto consistente 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!
Reloading...
=> 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 recargar.

7.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 eso se refleje en todas partes donde ya se usan. Técnicamente, "descargar" la clase User significa eliminar la constante User mediante Object.send(:remove_const, "User").

Por ejemplo, revisa esta sesión de 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 entonces evalúa a una clase recargada diferente. alice es una instancia de la recién cargada User, pero joe no lo es — su clase está obsoleta. Puedes definir joe nuevamente, iniciar una subsesión de IRB, o simplemente lanzar una nueva consola en lugar de llamar a reload!.

Otra situación en la que puedes encontrar este inconveniente es al subclasificar clases recargables en un lugar que no se recarga:

# lib/vip_user.rb
class VipUser < User
end

si User se recarga, dado que VipUser no lo es, la superclase de VipUser es el objeto de clase original obsoleto.

Conclusión: no caches clases o módulos recargables.

8 Autocarga Cuando la Aplicación se Inicia

Mientras se inicia, las aplicaciones pueden autocargar desde las rutas de autocarga única, que son gestionadas por el autocargador once. Por favor, consulta la sección config.autoload_once_paths arriba.

Sin embargo, no puedes autocargar desde las rutas de autocarga, que son gestionadas por el autocargador 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 la aplicación se inicia. No se ejecutan nuevamente en las recargas. Si un inicializador usara una clase o módulo recargable, las ediciones de ellos no se reflejarían en ese código inicial, volviéndose obsoletas. Por lo tanto, referirse a constantes recargables durante la inicialización no está permitido.

Veamos qué hacer en su lugar.

8.1 Caso de Uso 1: Durante el Inicio, Cargar Código Recargable

8.1.1 Autocargar al Inicio y en Cada Recarga

Imaginemos que ApiGateway es una clase recargable y necesitas configurar su endpoint mientras la aplicación se inicia:

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

Los inicializadores no pueden referirse a constantes recargables, necesitas envolver eso en un bloque to_prepare, que se ejecuta al 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, este callback puede ejecutarse dos veces. El código que ejecuta debe ser idempotente.

8.1.2 Autocargar Solo al Inicio

Las clases y módulos recargables también pueden ser autocargados en bloques after_initialize. Estos se ejecutan al inicio, pero no se ejecutan nuevamente en la recarga. En algunos casos excepcionales esto puede ser lo que deseas.

Las verificaciones previas 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 siembra la base de datos."
  end
end

8.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 obsoletos almacenados en caché.

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

Cualquiera que sea el objeto que MoneySerializer evalúe durante la inicialización se agrega a los serializadores personalizados, y ese objeto permanece allí en las recargas.

Otro ejemplo más son las railties o motores que decoran clases del marco 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 agrega 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 aún tendría el original.

Corolario: Esas clases o módulos no pueden ser recargables.

Una forma idiomática de organizar estos archivos es colocarlos en el directorio lib y cargarlos con require donde sea necesario. Por ejemplo, si la aplicación tiene middleware personalizado en lib/middleware, emite una llamada require regular antes de configurarlo:

require "middleware/my_middleware"
config.middleware.use MyMiddleware

Además, si lib está en las rutas de autocarga, configura el autocargador para ignorar ese subdirectorio:

# config/application.rb
config.autoload_lib(ignore: %w(assets tasks ... middleware))

ya que estás cargando esos archivos tú mismo.

Como se mencionó anteriormente, otra opción es tener el directorio que los define en las rutas de autocarga única y autocargar. Por favor, consulta la sección sobre config.autoload_once_paths para más detalles.

8.3 Caso de Uso 3: Configurar Clases de Aplicación para Motores

Supongamos que un motor trabaja con la clase de aplicación recargable que modela los usuarios, y tiene un punto de configuración para ella:

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

Para jugar bien con el código de la aplicación recargable, el motor en su lugar 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

Entonces, en tiempo de ejecución, config.user_model.constantize te da el objeto de clase actual.

9 Carga Anticipada

En entornos de producción o similares, generalmente es mejor cargar todo el código de la aplicación cuando la aplicación se inicia. La carga anticipada pone todo en memoria listo para atender solicitudes de inmediato, y también es amigable con CoW.

La carga anticipada se controla mediante la bandera config.eager_load, que está deshabilitada por defecto en todos los entornos excepto production. Cuando se ejecuta una tarea de Rake, config.eager_load se anula mediante config.rake_eager_load, que es false por defecto. Por lo tanto, por defecto, en entornos de producción las tareas de Rake no cargan anticipadamente la aplicación.

El orden en el que se cargan anticipadamente los archivos es indefinido.

Durante la carga anticipada, Rails invoca Zeitwerk::Loader.eager_load_all. Eso asegura que todas las dependencias de gemas gestionadas por Zeitwerk también se carguen anticipadamente.

10 Herencia de Tabla Única

La Herencia de Tabla Única no se lleva bien con la carga diferida: Active Record tiene que estar al tanto de las jerarquías de STI para funcionar correctamente, pero cuando se carga diferida, las clases se cargan precisamente solo bajo demanda.

Para abordar esta incompatibilidad fundamental, necesitamos precargar los STIs. Hay algunas opciones para lograr esto, con diferentes compensaciones. Veámoslas.

10.1 Opción 1: Habilitar Carga Anticipada

La forma más fácil de precargar los 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 inicio y en cada recarga. Sin embargo, la compensación puede valer la pena para aplicaciones pequeñas.

10.2 Opción 2: Precargar un Directorio Colapsado

Almacena los archivos que definen la jerarquía en un directorio dedicado, lo que también tiene sentido conceptualmente. El directorio no está destinado a 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 tu preferencia personal para mantener las cosas simples, y también evita refactorizaciones en bases de código existentes. La característica 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 inicio y recarga incluso si el STI no se usa. 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 fue agregado en Zeitwerk 2.6.2. Para versiones anteriores, aún puedes listar el directorio app/models/shapes e invocar require_dependency en sus contenidos.

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 STI separada a la aplicación, necesitarás editar el inicializador y reiniciar el servidor.

10.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 este, el inicializador es el mismo excepto que no se configura colapsar:

# 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.

10.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 métodos como subclasses o descendants no devolverán los tipos que faltan.

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 STI separada a la aplicación, necesitarás editar el inicializador y reiniciar el servidor.

11 Personalización de Inflexiones

Por defecto, Rails utiliza String#camelize para saber qué constante debería definir un nombre de archivo o directorio dado. Por ejemplo, posts_controller.rb debería definir PostsController porque eso es lo que "posts_controller".camelize devuelve.

Podría ser el caso de que algún nombre de archivo o directorio particular no se inflexione como deseas. Por ejemplo, html_parser.rb se espera que defina HtmlParser por defecto. ¿Qué pasa si prefieres que la clase sea HTMLParser? Hay algunas formas de personalizar esto.

La forma más fácil es definir acrónimos:

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

Hacer esto afecta cómo Active Support inflexiona globalmente. Eso puede estar bien en algunas aplicaciones, pero también puedes personalizar cómo camelizar nombres de base individuales independientemente de Active Support pasando una colección de sobrescrituras a los inflectores predeterminados:

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

Esa técnica aún depende de String#camelize, sin embargo, porque eso es lo que los inflectores predeterminados usan como respaldo. Si en su lugar prefieres no depender de las inflexiones de Active Support en absoluto y tener control absoluto sobre las inflexiones, configura los inflectores para que sean 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 configuración global que pueda afectar dichas instancias; son deterministas.

Incluso puedes definir un inflector personalizado para una flexibilidad total. Por favor, consulta la documentación de Zeitwerk para más detalles.

11.1 ¿Dónde Debería Ir la Personalización de Inflexiones?

Si una aplicación no usa el autocargador once, los fragmentos de código anteriores pueden ir en config/initializers. Por ejemplo, config/initializers/inflections.rb para el caso de uso de Active Support, o config/initializers/zeitwerk.rb para los otros.

Las aplicaciones que usan el autocargador once tienen que mover o cargar esta configuración desde el cuerpo de la clase de la aplicación en config/application.rb, porque el autocargador once usa el inflector temprano en el proceso de inicio.

12 Espacios de Nombres Personalizados

Como vimos anteriormente, las rutas de autocarga representan el espacio de nombres de nivel superior: Object.

Consideremos app/services, por ejemplo. Este directorio no se genera por defecto, pero si existe, Rails lo agrega automáticamente a las rutas de autocarga.

Por defecto, se espera que el archivo app/services/users/signup.rb defina Users::Signup, pero ¿qué pasa si prefieres 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 tu gusto, eso podría no parecerte correcto. Podrías preferir 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 puedes personalizar el autocargador main para lograrlo:

# config/initializers/autoloading.rb

# El espacio de nombres tiene que existir.
#
# En este ejemplo, definimos el módulo en el lugar. 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 característica, pero aún puedes 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" # tiene que 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 autocargador once. Sin embargo, dado que ese se configura más temprano en el proceso de inicio, la configuración no se puede hacer en un inicializador de aplicación. En su lugar, colócalo en config/application.rb, por ejemplo.

13 Autocarga y Motores

Los motores se ejecutan en el contexto de una aplicación principal, y su código es autocargado, recargado y cargado anticipadamente 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 de los motores se agregan a las rutas de autocarga, y desde el punto de vista del autocargador, no hay diferencia. Las entradas principales de los autocargadores son las rutas de autocarga, y si pertenecen al árbol de código fuente de la aplicación o al árbol de código fuente de algún motor es irrelevante.

Por ejemplo, esta aplicación usa 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 autocarga de su aplicación principal, el motor puede escribirse como de costumbre.

Sin embargo, si un motor admite Rails 6 o Rails 6.1 y no controla sus aplicaciones principales, tiene que estar listo para ejecutarse en modo classic o zeitwerk. Cosas a tener en cuenta:

  1. Si el modo classic necesitaría una llamada require_dependency para asegurar que alguna constante se cargue en algún momento, escríbela. Aunque zeitwerk no lo necesitaría, no hará daño, funcionará en modo zeitwerk también.

  2. El modo classic subraya los nombres de las constantes ("User" -> "user.rb"), y el modo zeitwerk cameliza los nombres de archivo ("user.rb" -> "User"). Coinciden en la mayoría de los casos, pero no lo hacen 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, elige "HtmlParser".

  3. En modo classic, el archivo app/model/concerns/foo.rb tiene permitido definir tanto Foo como Concerns::Foo. En modo zeitwerk, solo hay una opción: tiene que definir Foo. Para ser compatible, define Foo.

14 Pruebas

14.1 Pruebas Manuales

La tarea zeitwerk:check verifica si el árbol del proyecto sigue las convenciones de nomenclatura esperadas y es útil para verificaciones manuales. Por ejemplo, si estás migrando de modo classic a zeitwerk, o si estás corrigiendo algo:

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

Puede haber salida adicional dependiendo de la configuración de la aplicación, pero el último "All is good!" es lo que estás buscando.

14.2 Pruebas Automatizadas

Es una buena práctica verificar en el conjunto de pruebas que el proyecto se carga anticipadamente correctamente.

Eso cubre el cumplimiento de la nomenclatura de Zeitwerk y otras posibles condiciones de error. Por favor, consulta la sección sobre pruebas de carga anticipada en la guía Testing Rails Applications.

15 Solución de Problemas

La mejor manera de seguir lo que están haciendo los cargadores es inspeccionar su actividad.

La forma más fácil de hacer eso es incluir

Rails.autoloaders.log!

en config/application.rb después de cargar los valores predeterminados del marco. Eso imprimirá trazas en la salida estándar.

Si prefieres registrar en un archivo, configura 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 prefieres usar el registrador de Rails, configura esta opción en un inicializador en su lugar:

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

16 Rails.autoloaders

Las instancias de Zeitwerk que gestionan tu 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 ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.

También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.

Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.

Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.