1 Introduction
Esta guía documenta la carga automática en el modo zeitwerk
, que es nuevo en Rails 6. Si en su lugar desea leer sobre el modo clásico
, consulte Constantes de carga y recarga automática (modo clásico).
En un programa de Ruby normal, las dependencias deben cargarse a mano. Por ejemplo, el siguiente controlador usa las clases ApplicationController
y Post
, y normalmente necesitarías poner llamadas require
para ellas:
# DO NOT DO THIS.
require "application_controller"
require "post"
# DO NOT DO THIS.
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Este no es el caso de las aplicaciones Rails, donde las clases de aplicaciones y los módulos están disponibles en todas partes:
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Las aplicaciones de Idiomatic Rails solo emiten llamadas require
para cargar cosas desde su directorio lib
, la biblioteca estándar de Ruby, gemas Ruby, etc. Es decir, cualquier cosa que no pertenezca a sus rutas de carga automática, explicadas a continuación.
2 Enabling Zeitwerk Mode
El modo de carga automática zeitwerk
está habilitado de forma predeterminada en las aplicaciones de Rails 6 que se ejecutan en CRuby:
# config/application.rb
config.load_defaults 6.0 # enables zeitwerk mode in CRuby
En el modo zeitwerk
, Rails usa Zeitwerk internamente para autocargar, recargar y cargar ansioso. Rails crea una instancia y configura una instancia dedicada de Zeitwerk que administra el proyecto.
No se configura Zeitwerk manualmente en una aplicación Rails. En su lugar, configura la aplicación utilizando los puntos de configuración portátiles que se explican en esta guía, y Rails lo traduce a Zeitwerk en su nombre.
3 Project Structure
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
.
De forma predeterminada, Rails configura Zeitwerk para declinar los nombres de los archivos con String#camelize
. Por ejemplo, espera que app/controllers/users_controller.rb
defina el constante UsersController
porque
"users_controller".camelize # => UsersController
La sección Personalización de inflexiones a continuación documenta las formas de anular este valor predeterminado.
Por favor, consulte la documentación de Zeitwerk para obtener más detalles.
4 Autoload paths
Nos referimos a la lista de directorios de aplicaciones cuyo contenido se va a cargar automáticamente como rutas de carga automática. Por ejemplo, app/models
. Dichos directorios representan el espacio de nombres raíz: Object
.
Las rutas de carga automática se denominan directorios raíz en la documentación de Zeitwerk, pero nos quedaremos con la "ruta de carga automática" en esta guía.
Dentro de una ruta de carga automática, los nombres de los archivos deben coincidir con las constantes que definen como se documenta aquí.
De forma predeterminada, las rutas de carga automática de una aplicación constan de todos los subdirectorios de app
que existen cuando la aplicación arranca --- excepto para assets
, javascript
, views
, --- más las rutas de carga automática de los motores podría depender.
Por ejemplo, si UsersHelper
está implementado 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
Las rutas de carga automática eligen automáticamente cualquier directorio personalizado en la app
. Por ejemplo, si su aplicación tiene aplicación/presentadores
, o aplicación/servicios
, etc., se agregan a las rutas de carga automática.
La matriz de rutas de carga automática se puede ampliar mutando config.autoload_paths
, en config/application.rb
, pero hoy en día esto no se recomienda.
Por favor, no mute ActiveSupport::Dependencies.autoload_paths
, la interfaz pública para cambiar las rutas de carga automática es config.autoload_paths
.
5 $LOAD_PATH
Las rutas de carga automática se agregan a $LOAD_PATH
de forma predeterminada. Sin embargo, Zeitwerk usa nombres de archivo absolutos internamente, y su aplicación no debería emitir llamadas require
para archivos autocargables, por lo que esos directorios en realidad no son necesarios allí. Puede optar por no participar con esta bandera:
config.add_autoload_paths_to_load_path = false
Eso puede acelerar un poco las llamadas legítimas require
, ya que hay menos búsquedas. Además, si su aplicación usa Bootsnap, eso evita que la biblioteca cree índices innecesarios y guarda la RAM que necesitarían.
6 Reloading
Rails recarga automáticamente clases y módulos si cambian los archivos de la aplicación.
Más precisamente, si el servidor web se está ejecutando y los archivos de la aplicación se han modificado, Rails descarga todas las constantes cargadas automáticamente justo antes de que se procese la siguiente solicitud. De esa manera, las clases de aplicación o los módulos utilizados durante esa solicitud se cargarán automáticamente, recogiendo así 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.cache_classes
, que es falsa de forma predeterminada en el modo dedevelop
(recarga habilitada) y verdadera de forma predeterminada en el modo de production
(recarga deshabilitada).
Rails detecta que los archivos han cambiado usando un monitor de archivos de eventos (predeterminado) o recorriendo las rutas de carga automática, dependiendo de config.file_watcher
.
En una consola Rails no hay ningún observador de archivos activo independientemente del valor de config.cache_classes
. Esto se debe a que, normalmente, sería confuso tener el código recargado en medio de una sesión de consola, de la misma manera que generalmente desea que una solicitud individual sea atendida por un conjunto consistente y sin cambios de clases y módulos de aplicación.
Sin embargo, puede forzar una recarga en la consola ejecutando reload!
:
$ bin/rails c
Loading development environment (Rails 6.0.0)
irb(main):001:0> User.object_id
=> 70136277390120
irb(main):002:0> reload!
Reloading...
=> true
irb(main):003:0> User.object_id
=> 70136284426020
como puede ver, el objeto de clase almacenado en la constante User
es diferente después de la recarga.
6.1 Reloading and Stale Objects
Es muy importante entender que Ruby no tiene una forma de recargar realmente clases y módulos en la memoria, y eso se refleja en todos los lugares donde ya se usan. Técnicamente, "descargar" la clase User
significa eliminar la constante User
a través de Object.send(:remove_const, "User")
.
Por lo tanto, el código que hace referencia a una clase o módulo recargable, pero que no se ejecuta de nuevo al recargar, se vuelve obsoleto. Veamos un ejemplo a continuación.
Consideremos este inicializador:
# config/initializers/configure_payment_gateway.rb
# DO NOT DO THIS.
$PAYMENT_GATEWAY = Rails.env.production? ? RealGateway : MockedGateway
# DO NOT DO THIS.
La idea sería usar $PAYMENT_GATEWAY
en el código, y dejar que el inicializador lo establezca en la implementación real que depende del entorno.
En la recarga, MockedGateway
se vuelve a cargar, pero $PAYMENT_GATEWAY
no se actualiza porque los inicializadores solo se ejecutan al arrancar. Por lo tanto, no reflejará los cambios.
Hay varias formas de hacer esto de forma segura. Por ejemplo, la aplicación podría definir un método de clase PaymentGateway.impl
cuya definición depende del entorno; o podría definir "PaymentGateway" para tener una clase principal o mixin que depende del entorno; o use el mismo truco de variable global, pero en una devolución de llamada del recargador, como se explica a continuación.
Veamos otras situaciones que involucran objetos obsoletos de clase o módulo.
Consulta esta sesión de la consola de Rails:
> joe = User.new
> reload!
> alice = User.new
> 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 recargada diferente. alice
es una instancia del actual, pero joe
no lo es, su clase está obsoleta. Puede definir joe
nuevamente, iniciar una subsesión de IRB o simplemente iniciar una nueva consola en lugar de llamar a reload!
.
Otra situación en la que puede encontrar este problema es subclasificar clases recargables en un lugar que no se recarga:
# lib/vip_user.rb
class VipUser < User
end
si se vuelve a cargar User
, ya que VipUser
no lo es, la superclase de VipUser
es el objeto de clase obsoleto original.
En pocas palabras: no guarde en caché clases o módulos recargables.
6.2 Autoloading when the application boots
Las aplicaciones pueden cargar automáticamente constantes durante el arranque mediante una devolución de llamada del cargador:
Rails.application.reloader.to_prepare do
$PAYMENT_GATEWAY = Rails.env.production? ? RealGateway : MockedGateway
end
Ese bloque se ejecuta cuando se inicia la aplicación y cada vez que se vuelve a cargar el código.
Por razones históricas, esta devolución de llamada puede ejecutarse dos veces. El código que ejecuta debe ser idempotente.
7 Eager Loading
En entornos de producción, generalmente es mejor cargar todo el código de la aplicación cuando se inicia la aplicación. La carga ansiosa pone todo en la memoria listo para atender solicitudes de inmediato, y también es compatible con CoW.
La carga ansiosa está controlada por la bandera config.eager_load
, que está habilitada por defecto en el modo producción
.
El orden en el que se cargan los archivos no está definido.
si la constante Zeitwerk
está definida, Rails invoca Zeitwerk::Loader.eager_load_all
independientemente del modo de carga automática de la aplicación. Eso asegura que las dependencias administradas por Zeitwerk estén cargadas con entusiasmo.
8 Single Table Inheritance
La herencia de tabla única es una función que no funciona bien con la carga diferida. La razón es que su API generalmente necesita poder enumerar la jerarquía STI para que funcione correctamente, mientras que la carga diferida difiere la carga de clases hasta que se hace referencia a ellas. No puede enumerar lo que aún no ha hecho referencia.
En cierto sentido, las aplicaciones necesitan ansiosamente cargar las jerarquías de STI independientemente del modo de carga.
Por supuesto, si la aplicación se carga ansiosamente al arrancar, eso ya está logrado. Cuando no es así, en la práctica es suficiente instanciar los tipos existentes en la base de datos, lo que en los modos de desarrollo o prueba suele estar bien. Una forma de hacerlo es lanzar este módulo al directorio lib
:
module StiPreload
unless Rails.application.config.eager_load
extend ActiveSupport::Concern
included do
cattr_accessor :preloaded, instance_accessor: false
end
class_methods do
def descendants
preload_sti unless preloaded
super
end
# Constantizes all types present in the database. There might be more on
# disk, but that does not matter in practice as far as the STI API is
# concerned.
#
# Assumes store_full_sti_class is true, the default.
def preload_sti
types_in_db = \
base_class.
unscoped.
select(inheritance_column).
distinct.
pluck(inheritance_column).
compact
types_in_db.each do |type|
logger.debug("Preloading STI type #{type}")
type.constantize
end
self.preloaded = true
end
end
end
end
y luego inclúyalo en las clases raíz STI de su proyecto:
# app/models/shape.rb
require "sti_preload"
class Shape < ApplicationRecord
include StiPreload # Only in the root class.
end
# app/models/polygon.rb
class Polygon < Shape
end
# app/models/triangle.rb
class Triangle < Polygon
end
9 Customizing Inflections
Por defecto, Rails usa String#camelize
para saber qué constante debe definir un archivo o nombre de directorio dado. Por ejemplo, posts_controller.rb
debería definir PostsController
porque eso es lo que devuelve "posts_controller".camelize
.
Podría darse el caso de que algún nombre de directorio o archivo en particular no se flexione como desea. 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 en config/initializers/inflections.rb
:
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym "HTML"
inflect.acronym "SSL"
end
Si lo hace, afectará a la forma en que Active Support se flexiona globalmente. Eso puede estar bien en algunas aplicaciones, pero también puede personalizar cómo camelizar nombres de base individuales independientemente de Active Support pasando una colección de anulaciones a los inflectores predeterminados:
# config/initializers/zeitwerk.rb
Rails.autoloaders.each do |autoloader|
autoloader.inflector.inflect(
"html_parser" => "HTMLParser",
"ssl_error" => "SSLError"
)
end
Sin embargo, esa técnica todavía depende de String#camelize
, porque eso es lo que los inflectores predeterminados usan como respaldo. Si en cambio prefiere no depender de las inflexiones de Active Support y tener un control absoluto sobre las inflexiones, configure los inflectores para que sean instancias de Zeitwerk::Inflector
:
# config/initializers/zeitwerk.rb
Rails.autoloaders.each do |autoloader|
autoloader.inflector = Zeitwerk::Inflector.new
autoloader.inflector.inflect(
"html_parser" => "HTMLParser",
"ssl_error" => "SSLError"
)
end
No existe una configuración global que pueda afectar a dichas instancias, son deterministas.
Incluso puede definir un inflector personalizado para una flexibilidad total. Por favor, consulte la documentación de Zeitwerk para obtener más detalles.
10 Troubleshooting
La mejor forma de seguir lo que están haciendo los cargadores es inspeccionar su actividad.
La forma más sencilla de hacerlo es lanzar
Rails.autoloaders.log!
a config/application.rb
después de cargar los valores predeterminados del marco. Eso imprimirá trazas en la salida estándar.
Si prefiere iniciar sesión en un archivo, configure esto en su lugar:
Rails.autoloaders.logger = Logger.new("#{Rails.root}/log/autoloading.log")
El registrador Rails todavía no está listo en config/application.rb
, pero está en inicializadores:
# config/initializers/log_autoloaders.rb
Rails.autoloaders.logger = Rails.logger
11 Rails.autoloaders
Las instancias de Zeitwerk que administran su aplicación están disponibles en
Rails.autoloaders.main
Rails.autoloaders.once
El primero es el principal. Este último está allí principalmente por razones de compatibilidad con versiones anteriores, en caso de que la aplicación tenga algo en config.autoload_once_paths
(esto no se recomienda hoy en día).
Puede comprobar si el modo zeitwerk
está habilitado con
Rails.autoloaders.zeitwerk_enabled?
12 Opting Out
Las aplicaciones pueden cargar los valores predeterminados de Rails 6 y seguir usando el cargador automático clásico de esta manera:
# config/application.rb
config.load_defaults 6.0
config.autoloader = :classic
Eso puede ser útil si se actualiza a Rails 6 en diferentes fases, pero se desaconseja el modo clásico para nuevas aplicaciones.
El modo zeitwerk
no está disponible en versiones de Rails anteriores a 6.0.
Comentarios Sobre el Contenido
Las guías de rieles se administran y publican en latinadeveloper/railsguides.es en GitHub.
Si lee esta guía y encuentra algún texto o código incorrecto que le interese, no dude en enviar una solicitud de extracción en el repositorio anterior. Consulte el archivo README en GitHub para saber cómo enviar una solicitud de extracción. Please contribute if you see any typos or factual errors.