Más en rubyonrails.org: Más Ruby on Rails

NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS SE PUBLICAN EN https://railsguides.es/

Constantes de Autocarga y Recarga (modo Zeitwerk)

Esta guía documenta cómo funcionan la carga y la recarga automáticas constantes en el modo "clásico".

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

1 Introduction

Esta guía documenta la carga automática en el modo clásico, que es el tradicional. Si desea leer sobre el modo zeitwerk en su lugar, el nuevo en Rails 6, verifique Constantes de carga y carga automática (modo Zeitwerk).

Ruby on Rails permite que las aplicaciones se escriban como si su código estuviera precargado.

En un programa normal de Ruby, las clases necesitan cargar sus dependencias:

require "application_controller"
require "post"

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

Nuestro instinto rubyista ve rápidamente algo de redundancia allí: si las clases fueran definido en archivos que coincidan con su nombre, ¿no podría automatizarse su carga? ¿de algun modo? Podríamos ahorrar escaneando el archivo en busca de dependencias, lo cual es frágil.

Además, Kernel#require carga archivos una vez, pero el desarrollo es mucho más fluido si el código se actualiza cuando cambia sin reiniciar el servidor. Sería Sería bueno poder usar Kernel#load en desarrollo, y Kernel#require en producción.

De hecho, esas funciones las proporciona Ruby on Rails, donde simplemente escribimos

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

Esta guía documenta cómo funciona.

2 Constants Refresher

Si bien las constantes son triviales en la mayoría de los lenguajes de programación, son una rica tema en Ruby.

Está más allá del alcance de esta guía documentar las constantes de Ruby, pero estamos no obstante, voy a destacar algunos temas clave. Verdaderamente captando lo siguiente secciones es fundamental para comprender la carga y la recarga automáticas constantes.

2.1 Nesting

Las definiciones de clases y módulos se pueden anidar para crear espacios de nombres:

module XML
  class SAXParser
    # (1)
  end
end

El anidamiento en cualquier lugar dado es la colección de clases anidadas adjuntas y los objetos del módulo hacia afuera. El anidamiento en cualquier lugar dado se puede inspeccionar con Module.nesting. Por ejemplo, en el ejemplo anterior, el anidamiento en (1) es

[XML::SAXParser, XML]

Es importante comprender que el anidamiento se compone de clase y módulo objetos, no tiene nada que ver con las constantes utilizadas para acceder a ellos, y es también sin relación con sus nombres.

Por ejemplo, si bien esta definición es similar a la anterior:

class XML::SAXParser
  # (2)
end

el anidamiento en (2) es diferente:

[XML::SAXParser]

XML no le pertenece.

Podemos ver en este ejemplo que el nombre de una clase o módulo que pertenece a un cierto anidamiento no se correlaciona necesariamente con los espacios de nombres en el lugar.

Más aún, son totalmente independientes, tomemos por ejemplo

module X
  module Y
  end
end

module A
  module B
  end
end

module X::Y
  module A::B
    # (3)
  end
end

El anidamiento en (3) consta de dos objetos de módulo:

[A::B, X::Y]

Entonces, no solo no termina en A, que ni siquiera pertenece al anidamiento, pero también contiene X::Y, que es independiente de A::B.

El anidamiento es una pila interna mantenida por el intérprete, y se obtiene modificado de acuerdo con estas reglas:

  • El objeto de clase que sigue a una palabra clave class se inserta cuando su cuerpo es ejecutado, y apareció tras él.

  • El objeto de módulo que sigue a una palabra clave module se envía cuando su cuerpo ejecutado, y apareció tras él.

  • Una clase singleton abierta con class << object es empujada y aparece más tarde.

  • Cuando se llama a instance_eval usando un argumento de cadena, la clase singleton del receptor se empuja al anidamiento del eval'ed código. Cuando se llama a class_eval o module_eval usando un argumento de cadena, el receptor es empujado al anidamiento del código evaluado.

  • El anidamiento en el nivel superior de código interpretado por Kernel#load está vacío a menos que la llamada load reciba un valor verdadero como segundo argumento, en cuyo caso Ruby envía un módulo anónimo recién creado.

Es interesante observar que los bloques no modifican la pila. En particular los bloques que se pueden pasar a Class.new y Module.new no obtienen el clase o módulo que se define empujado a su anidamiento. Ese es uno de los diferencias entre definir clases y módulos de una forma u otra.

2.2 Class and Module Definitions are Constant Assignments

Supongamos que el siguiente fragmento crea una clase (en lugar de volver a abrirla):

class C
end

Ruby crea una constante C en Object y almacena en esa constante una clase objeto. El nombre de la instancia de la clase es "C", una cadena que recibe el nombre de constante.

Es decir,

class Project < ApplicationRecord
end

realiza una asignación constante equivalente a

Project = Class.new(ApplicationRecord)

including setting the name of the class as a side-effect:

Project.name # => "Project"

La asignación constante tiene una regla especial para que eso suceda: si el objeto que se asigna es una clase o módulo anónimo, Ruby establece el nombre del objeto en el nombre de la constante.

A partir de entonces, lo que sucede con la constante y la instancia no importar. Por ejemplo, la constante podría eliminarse, el objeto de clase podría ser asignado a una constante diferente, ya no se almacenará en una constante, etc. el nombre está establecido, no cambia.

De manera similar, la creación de módulos usando la palabra clave module como en

module Admin
end

realiza una asignación constante equivalente a

Admin = Module.new

including setting the name as a side-effect:

Admin.name # => "Admin"

El contexto de ejecución de un bloque pasado a Class.new o Module.new no es enteramente equivalente a la del cuerpo de las definiciones utilizando el Palabras clave class y module. Pero ambos modismos resultan en la misma constante asignación.

Por lo tanto, una expresión informal como "la clase String " significa técnicamente la objeto de clase almacenado en la constante llamada "Cadena". Esa constante, a su vez, pertenece al objeto de clase almacenado en la constante denominada "Objeto".

String es una constante ordinaria, y todo lo relacionado con ellos, como se le aplican algoritmos de resolución.

Asimismo, en el controlador

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

Post no es la sintaxis de una clase. Más bien, Post es una constante de Ruby regular. Si todo es bueno, la constante se evalúa a un objeto que responde a "todo".

Por eso hablamos de carga automática * constante *, Rails tiene la capacidad de cargar constantes sobre la marcha.

2.3 Constants are Stored in Modules

Las constantes pertenecen a módulos en un sentido muy literal. Las clases y los módulos tienen una tabla constante; Piense en ello como una tabla hash.

Analicemos un ejemplo para entender realmente lo que eso significa. Aunque es común los abusos del lenguaje como "la clase String "son convenientes, la exposición es va a ser preciso aquí con fines didácticos.

Consideremos la siguiente definición de módulo:

module Colors
  RED = '0xff0000'
end

Primero, cuando se procesa la palabra clave module, el intérprete crea una nueva entrada en la tabla de constantes del objeto de clase almacenado en la constante Object. Dicha entrada asocia el nombre "Colores" a un objeto de módulo recién creado. Además, el intérprete establece el nombre del nuevo objeto de módulo como el cadena "Colores".

Posteriormente, cuando se interpreta el cuerpo de la definición del módulo, se crea una nueva entrada creado en la tabla constante del objeto módulo almacenado en los Colors constante. Esa entrada asigna el nombre "RED" a la cadena "0xff0000".

En particular, Colors::RED no tiene ninguna relación con ninguna otra constante de RED que puede vivir en cualquier otro objeto de clase o módulo. Si hubiera alguno, ellos tendría entradas separadas en sus respectivas tablas de constantes.

Preste especial atención en los párrafos anteriores a la distinción entre Objetos de clase y módulo, nombres de constantes y objetos de valor asociados a ellos. en tablas constantes.

2.4 Resolution Algorithms

2.4.1 Resolution Algorithm for Relative Constants

En cualquier lugar del código, definamos cref como el primer elemento de el anidamiento si no está vacío, o el Object en caso contrario.

Sin entrar demasiado en los detalles, el algoritmo de resolución para las referencias constantes son así:

  1. Si el anidamiento no está vacío, la constante se busca en sus elementos y en orden. Los antepasados ​​de esos elementos se ignoran.

  2. Si no se encuentra, entonces el algoritmo recorre la cadena de ancestros de la cref.

  3. Si no se encuentra y el cref es un módulo, la constante se busca en "Objeto".

  4. Si no se encuentra, se invoca const_missing en la cref. El valor por defecto la implementación de const_missing genera NameError, pero puede ser anulado.

La carga automática de rieles ** no emula este algoritmo **, pero su punto de partida es el nombre de la constante que se va a cargar automáticamente y la cref. Ver más en Relativo Referencias.

2.4.2 Resolution Algorithm for Qualified Constants

Las constantes calificadas tienen este aspecto:

Billing::Invoice

Billing::Invoice se compone de dos constantes: Billing es relativo y es resuelto utilizando el algoritmo del apartado anterior.

Los dos puntos iniciales harían que el primer segmento sea absoluto en lugar de relativo: ::Billing::Invoice. Eso obligaría a buscar "Facturación" solo como una constante de nivel superior.

Invoice por otro lado está calificado por Billing y vamos a ver su resolución a continuación. Definamos * parent * como esa clase o módulo calificado objeto, es decir, "Facturación" en el ejemplo anterior. El algoritmo para calificado constantes va así:

  1. La constante se busca en el padre y sus antepasados. En Ruby> = 2.5, Object se omite si está presente entre los antepasados. Kernel y BasicObject aunque todavía están marcados.

  2. Si la búsqueda falla, se invoca const_missing en el padre. El valor por defecto la implementación de const_missing genera NameError, pero puede ser anulado.

En Ruby <2.5 String::Hash se evalúa comoHash y el intérprete emite una advertencia: "Hash constante de nivel superior referenciado por String :: Hash". Comenzando con 2.5, String::Hash genera NameError porque se omite Object.

Como ves, este algoritmo es más simple que el de las constantes relativas. En En particular, el anidamiento no juega ningún papel aquí, y los módulos no tienen una carcasa especial, si ni ellos ni sus antepasados ​​tienen las constantes, Object es ** no ** comprobado.

La carga automática de rieles ** no emula este algoritmo **, pero su punto de partida es el nombre de la constante que se va a cargar automáticamente y el padre. Ver más en Referencias calificadas.

3 Vocabulary

3.1 Parent Namespaces

Dada una cadena con una ruta constante, definimos su * espacio de nombres principal * como el cadena que resulta de eliminar su segmento más a la derecha.

Por ejemplo, el espacio de nombres principal de la cadena "A::B::C" es la cadena "A::B", el espacio de nombres principal de "A::B" es "A", y el espacio de nombres principal de "A" es "".

La interpretación de un espacio de nombres principal al pensar en clases y módulos aunque es complicado. Consideremos un módulo M llamado "A::B":

  • El espacio de nombres principal, "A", puede no reflejar el anidamiento en un lugar determinado.

  • Es posible que la constante A ya no exista, algún código podría haberla eliminado de Objeto.

  • Si existe "A", es posible que la clase o módulo que originalmente estaba en "A" no esté allí nunca más. Por ejemplo, si después de una remoción constante hubo otra constante asignación, generalmente habría un objeto diferente allí.

  • En tal caso, incluso podría suceder que el "A" reasignado tuviera una nueva clase o módulo llamado también "A"!

  • En los escenarios anteriores, M ya no sería accesible a través de A::B pero el objeto del módulo en sí podría estar vivo en algún lugar y su nombre sería seguirá siendo "A::B".

La idea de un espacio de nombres principal es el núcleo de los algoritmos de carga automática y ayuda a explicar y comprender su motivación de manera intuitiva, pero como ve esa metáfora se filtra fácilmente. Dado un caso límite sobre el que razonar, tenga siempre en cuenta Tenga en cuenta que por "espacio de nombres principal" la guía se refiere exactamente a esa cadena específica derivación.

3.2 Loading Mechanism

Rails carga automáticamente archivos con Kernel#load cuando config.cache_classes es falso, el valor predeterminado en el modo de desarrollo, y con Kernel#require de lo contrario, el predeterminado en modo de producción.

Kernel#load permite a Rails ejecutar archivos más de una vez si constante recarga está habilitada.

Esta guía utiliza la palabra "cargar" libremente para indicar que se interpreta un archivo determinado, pero el mecanismo real puede ser Kernel#load o Kernel#require dependiendo de eso bandera.

4 Autoloading Availability

Rails siempre se puede cargar automáticamente siempre que su entorno esté en su lugar. por ejemplo, el comando runner se carga automáticamente:

`` bash $ bin / rails runner 'p User.column_names' ["id", "email", "created_at", "updated_at"] ''

La consola se carga automáticamente, la suite de pruebas se carga automáticamente y, por supuesto, la aplicación cargas automáticas.

De forma predeterminada, Rails ansioso carga los archivos de la aplicación cuando arranca en producción modo, por lo que la mayor parte de la carga automática que ocurre en el desarrollo no ocurre. Pero la carga automática aún puede activarse durante la carga ansiosa.

Por ejemplo, dado

class BeachHouse < House
end

si todavía se desconoce House cuando app/models/beach_house.rb está ansioso cargado, Rails lo carga automáticamente.

5 autoload_paths and eager_load_paths

Como probablemente sepa, cuando require obtiene un nombre de archivo relativo:

require "erb"

Ruby busca el archivo en los directorios listados en $LOAD_PATH. Es decir, ruby itera sobre todos sus directorios y para cada uno de ellos comprueba si tener un archivo llamado "erb.rb", o "erb.so", o "erb.o", o "erb.dll". Si encuentra cualquiera de ellos, el intérprete lo carga y finaliza la búsqueda. De lo contrario, intenta nuevamente en el siguiente directorio de la lista. Si la lista se agota, LoadError es elevado.

Más adelante cubriremos cómo funciona la carga automática constante con más detalle, pero la idea es que cuando una constante como Post se activa y falta, si hay una El archivo post.rb por ejemplo en app/models Rails lo encontrará, evalúe y tener Post definido como un efecto secundario.

Muy bien, Rails tiene una colección de directorios similar a $LOAD_PATH en la que para buscar post.rb. Esa colección se llama autoload_paths y por por defecto contiene:

  • Todos los subdirectorios de app en la aplicación y motores presentes en el arranque hora. Por ejemplo, app / controllers. No es necesario que sean los predeterminados unos, cualquier directorio personalizado como app/workers pertenece automáticamente a autoload_paths.

  • Cualquier directorio de segundo nivel existente llamado app/*/concern en el aplicación y motores.

  • El directorio test/mailers/previews.

eager_load_paths es inicialmente las rutas de la app anteriores

La forma en que se cargan automáticamente los archivos depende de los ajustes de configuración de eager_load y cache_classes que normalmente varían en los modos de desarrollo, producción y prueba:

  • En desarrollo, desea un inicio más rápido con carga incremental del código de la aplicación. Por lo tanto, eager_load debe establecerse en false, y Rails cargará automáticamente los archivos según sea necesario (consulte Algoritmos de carga automática a continuación) y luego los volverá a cargar cuando cambien (consulte Constant Reloading a continuación).
  • En producción, sin embargo, desea consistencia y seguridad de subprocesos y puede vivir con un tiempo de arranque más largo. Entonces, eager_load se establece en true, y luego, durante el arranque (antes de que la aplicación esté lista para recibir solicitudes), Rails carga todos los archivos en eager_load_paths y luego desactiva la carga automática (NB: la carga automática puede ser necesaria durante la carga ansiosa ). No cargar automáticamente después del arranque es algo bueno, ya que la carga automática puede hacer que la aplicación tenga problemas de seguridad de subprocesos.
  • En prueba, para la velocidad de ejecución (de pruebas individuales), eager_load es false, por lo que Rails sigue el comportamiento de desarrollo.

Lo que se describe arriba son los valores predeterminados con una aplicación Rails recién generada. Hay varias formas en que esto se puede configurar de manera diferente (consulte Configuración de aplicaciones de Rails. ). Pero usando autoload_paths por sí solo en el pasado (antes de Rails 5), los desarrolladores podían configurar autoload_paths para agregar ubicaciones adicionales (por ejemplo, lib, que solía ser una lista de rutas de autocarga hace años, pero ya no lo es). Sin embargo, esto ahora se desaconseja para la mayoría de los propósitos, ya que es probable que dé lugar a errores solo de producción. Es posible agregar nuevas ubicaciones tanto a config.eager_load_paths como a config.autoload_paths pero utilícelo bajo su propio riesgo.

Ver también Carga Automática en el Entorno de Prueba.

Se puede inspeccionar el valor de autoload_paths. En una aplicación recién generada es (editado):

$ bin/rails runner 'puts ActiveSupport::Dependencies.autoload_paths'
.../app/assets
.../app/channels
.../app/controllers
.../app/controllers/concerns
.../app/helpers
.../app/jobs
.../app/mailers
.../app/models
.../app/models/concerns
.../activestorage/app/assets
.../activestorage/app/controllers
.../activestorage/app/javascript
.../activestorage/app/jobs
.../activestorage/app/models
.../actioncable/app/assets
.../actionview/app/assets
.../test/mailers/previews

autoload_paths se calcula y almacena en caché durante el proceso de inicialización. La aplicación debe reiniciarse para reflejar cualquier cambio en el directorio. estructura.

6 Autoloading Algorithms

6.1 Relative References

Una referencia constante relativa puede aparecer en varios lugares, por ejemplo, en

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

las tres referencias constantes son relativas.

6.1.1 Constants after the class and module Keywords

Ruby realiza una búsqueda de la constante que sigue a una class o un module palabra clave porque necesita saber si la clase o módulo se va a crear o reabierto.

Si la constante no está definida en ese punto, no se considera una Falta constante, la carga automática ** no ** se activa.

Entonces, en el ejemplo anterior, si PostsController no está definido cuando el archivo se interpreta que la carga automática de Rails no se activará, Ruby simplemente definir el controlador.

6.1.2 Top-Level Constants

Por el contrario, si se desconoce ApplicationController, la constante es se considera perdido y Rails intentará una carga automática.

Para cargar ApplicationController, Rails itera sobre autoload_paths. Primero verifica si existe app/assets/application_controller.rb. Si no es así, que es normalmente el caso, continúa y encuentra app/controllers/application_controller.rb.

Si el archivo define la constante ApplicationController, todo está bien, de lo contrario Se genera LoadError:

'' no se puede cargar automáticamente ApplicationController constante, esperado para definirlo (LoadError) ''

Rails no requiere que el valor de las constantes autocargadas sea una clase o objeto de módulo. Por ejemplo, si el archivo app/models/max_clients.rb define MAX_CLIENTS = 100 la carga automática de MAX_CLIENTS funciona bien.

6.1.3 Namespaces

La carga automática de ApplicationController busca directamente debajo de los directorios de autoload_paths porque el nido en ese lugar está vacío. La situación de Post es diferente, el anidamiento en esa línea es [PostsController]y support para espacios de nombres entra en juego.

La idea básica es que dada

module Admin
  class BaseController < ApplicationController
    @@all_roles = Role.all
  end
end

para autocargar Role vamos a comprobar si está definido en el actual o espacios de nombres de los padres, uno a la vez. Entonces, conceptualmente queremos intentar cargar automáticamente cualquiera de

Admin::BaseController::Role
Admin::Role
Role

en ese orden. Esa es la idea. Para hacerlo, Rails busca en autoload_paths respectivamente para nombres de archivo como estos:

admin/base_controller/role.rb
admin/role.rb
role.rb

módulo algunas búsquedas de directorio adicionales que vamos a cubrir pronto.

'Constant::Name'.undercore da la ruta relativa sin extensión de el nombre del archivo donde se espera que se defina Constant::Name.

Veamos cómo Rails carga automáticamente la constante Post en el PostsController arriba asumiendo que la aplicación tiene un modelo Post definido en app/models/post.rb.

Primero busca posts_controller/post.rb en autoload_paths:

app/assets/posts_controller/post.rb
app/controllers/posts_controller/post.rb
app/helpers/posts_controller/post.rb
...
test/mailers/previews/posts_controller/post.rb

Dado que la búsqueda se agota sin éxito, una búsqueda similar de un directorio se realiza, vamos a ver por qué en la next section:

app/assets/posts_controller/post
app/controllers/posts_controller/post
app/helpers/posts_controller/post
...
test/mailers/previews/posts_controller/post

Si todos esos intentos fallan, Rails vuelve a iniciar la búsqueda en el archivo principal. espacio de nombres. En este caso, solo queda el nivel superior:

app/assets/post.rb
app/controllers/post.rb
app/helpers/post.rb
app/mailers/post.rb
app/models/post.rb

Se encuentra un archivo coincidente en app/models/post.rb. La búsqueda se detiene allí y el se carga el archivo. Si el archivo realmente define "Publicar", todo está bien, de lo contrario Se genera LoadError.

6.2 Qualified References

Cuando falta una constante calificada, Rails no la busca en el padre espacios de nombres. Pero hay una advertencia: cuando falta una constante, Rails es incapaz de saber si el disparador era una referencia relativa o calificada.

Por ejemplo, considere

module Admin
  User
end

y

Admin::User

Si falta User, en cualquier caso, todo lo que Rails sabe es que una constante llamada Faltaba "Usuario" en un módulo llamado "Admin".

Si hay un User de nivel superior, Ruby lo resolvería en el ejemplo anterior, pero no lo haría en el último. En general, Rails no emula la constante de Ruby algoritmos de resolución, pero en este caso intenta utilizar la siguiente heurística:

Si ninguno de los espacios de nombres principales de la clase o módulo tiene el constante, Rails asume que la referencia es relativa. De lo contrario calificado.

Por ejemplo, si este código activa la carga automática

Admin::User

y la constante User ya está presente en Object, no es posible que la situación es

module Admin
  User
end

porque de lo contrario Ruby habría resuelto User y ninguna carga automática habría se ha activado en primer lugar. Por lo tanto, Rails asume una referencia calificada y considera que el archivo admin/user.rb y el directorio admin/user son los únicos opciones válidas.

En la práctica, esto funciona bastante bien siempre que el anidamiento coincida con todos los padres espacios de nombres respectivamente y las constantes que hacen que la regla se aplique se conocen en ese momento.

Sin embargo, la carga automática se realiza a pedido. Si por casualidad el User de nivel superior aún no cargado, Rails asume una referencia relativa por contrato.

Los conflictos de nombres de este tipo son raros en la práctica, pero si ocurre alguno, require_dependency proporciona una solución asegurando que la constante necesaria desencadenar la heurística se define en el lugar conflictivo.

6.3 Automatic Modules

Cuando un módulo actúa como un espacio de nombres, Rails no requiere que la aplicación definir un archivo para él, un directorio que coincida con el espacio de nombres es suficiente.

Suponga que una aplicación tiene un back office cuyos controladores se almacenan en app/controllers/admin. Si el módulo Admin aún no está cargado cuando Admin::UsersController se activa, Rails necesita primero cargar automáticamente la constante Admin.

Si autoload_paths tiene un archivo llamado admin.rb Rails lo cargará uno, pero si no existe tal archivo y se encuentra un directorio llamado admin, Rails crea un módulo vacío y lo asigna a la constante Admin sobre la marcha.

6.4 Generic Procedure

Se informa que faltan referencias relativas en la cuadrilla donde fueron golpeadas, y se informa que faltan referencias calificadas en su padre (ver Algoritmo de resolución para relativo Constants al comienzo de esta guía para la definición de * cref , y algoritmo de resolución para calificados Constantes para la definición de *padre).

El procedimiento para cargar automáticamente la constante C en una situación arbitraria es el siguiente:

if the class or module in which C is missing is Object
  let ns = ''
else
  let M = the class or module in which C is missing

  if M is anonymous
    let ns = ''
  else
    let ns = M.name
  end
end

loop do
  # Look for a regular file.
  for dir in autoload_paths
    if the file "#{dir}/#{ns.underscore}/c.rb" exists
      load/require "#{dir}/#{ns.underscore}/c.rb"

      if C is now defined
        return
      else
        raise LoadError
      end
    end
  end

  # Look for an automatic module.
  for dir in autoload_paths
    if the directory "#{dir}/#{ns.underscore}/c" exists
      if ns is an empty string
        let C = Module.new in Object and return
      else
        let C = Module.new in ns.constantize and return
      end
    end
  end

  if ns is empty
    # We reached the top-level without finding the constant.
    raise NameError
  else
    if C exists in any of the parent namespaces
      # Qualified constants heuristic.
      raise NameError
    else
      # Try again in the parent namespace.
      let ns = the parent namespace of ns and retry
    end
  end
end

7 require_dependency

La carga automática constante se activa a pedido y, por lo tanto, el código que utiliza un cierta constante puede tenerlo ya definido o puede activar una carga automática. Ese depende de la ruta de ejecución y puede variar entre ejecuciones.

Sin embargo, hay ocasiones en las que desea asegurarse de que cierta constante conocido cuando la ejecución alcanza algún código. require_dependency proporciona una forma para cargar un archivo usando el mecanismo de carga, y realizar un seguimiento de las constantes definidas en ese archivo como si estuvieran cargadas automáticamente para Hágalos recargar según sea necesario.

require_dependency rara vez se necesita, pero vea un par de casos de uso en Autoloading y STI y Cuando las constantes no son Activado.

A diferencia de la carga automática, require_dependency no espera que el archivo definir cualquier constante particular. Explotar este comportamiento sería una mala práctica sin embargo, las rutas de archivo y constantes deben coincidir.

8 Constant Reloading

Cuando config.cache_classes es falso, Rails puede recargar autocargado constantes.

Por ejemplo, si está en una sesión de consola y edita algún archivo detrás del escenas, el código se puede recargar con el comando reload!:

> reload!

Cuando se ejecuta la aplicación, el código se vuelve a cargar cuando algo relevante para esto cambios de lógica. Para hacer eso, Rails monitorea una serie de cosas:

  • config/routes.rb.

  • Locales.

  • Archivos Ruby​debajo autoload_paths.

  • db/schema.rb y db/structure.sql.

Si algo cambia allí, hay un middleware que lo detecta y vuelve a cargar el código.

La carga automática realiza un seguimiento de las constantes de carga automática. La recarga es implementada por eliminándolos todos de sus respectivas clases y módulos usando Módulo#remove_const. De esa forma, cuando el código continúa, esas constantes son volverá a ser desconocido y los archivos se volverán a cargar a pedido.

Esta es una operación de todo o nada, Rails no intenta recargar solo lo que cambió desde las dependencias entre clases lo hace realmente complicado. En cambio, todo se borra.

9 Common Gotchas

9.1 Nesting and Qualified Constants

Consideremos

module Admin
  class UsersController < ApplicationController
    def index
      @users = User.all
    end
  end
end

y

class Admin::UsersController < ApplicationController
  def index
    @users = User.all
  end
end

Para resolver User Ruby verifica Admin en el primer caso, pero no lo hace en el último porque no pertenece al anidamiento (ver Anidamiento y Algoritmos de resolución).

Desafortunadamente, la carga automática de Rails no conoce el anidamiento en el lugar donde Faltaba una constante y, por lo tanto, no puede actuar como lo haría Ruby. En particular, Admin::User se cargará automáticamente en cualquier caso.

Aunque las constantes calificadas con palabras clave class y module pueden técnicamente trabajar con carga automática en algunos casos, es preferible utilizar constantes relativas en lugar:

module Admin
  class UsersController < ApplicationController
    def index
      @users = User.all
    end
  end
end

9.2 Defining vs Reopening Namespaces

Let's consider:

# app/models/blog.rb
module Blog
  def self.table_name_prefix
    "blog_"
  end
end

# app/models/blog/post.rb
module Blog
  class Post < ApplicationRecord
  end
end

El nombre de la tabla para Blog::Post debería ser blog_posts debido a la existencia de el método Blog.table_name_prefix. Sin embargo, si app/models/blog/post.rb es ejecutado antes de que app/models/blog.rb sea, Active Record no es consciente del existencia de tal método, y asume que la tabla es "posts".

Para resolver una situación como esta, es útil pensar claramente en qué archivo define el módulo Blog (app/models/blog.rb), y cuál reabre (app/models/blog/post.rb). Luego, te aseguras de que la definición se ejecute primero usando require_dependency:

# app/models/blog/post.rb

require_dependency "blog"

module Blog
  class Post < ApplicationRecord
  end
end

9.3 Autoloading and STI

La herencia de tabla única (STI) es una función de Active Record que permite almacenar una jerarquía de modelos en una sola tabla. La API de tales modelos es consciente de la jerarquía y encapsula algunas necesidades comunes. Por ejemplo, dado estas clases:

# app/models/polygon.rb
class Polygon < ApplicationRecord
end

# app/models/triangle.rb
class Triangle < Polygon
end

# app/models/rectangle.rb
class Rectangle < Polygon
end

Triangle.create crea una fila que representa un triángulo, y Rectangle.create crea una fila que representa un rectángulo. Si id es el ID de un registro existente, Polygon.find(id) devuelve un objeto de la correcta tipo.

Los métodos que operan en colecciones también conocen la jerarquía. por ejemplo, Polygon.all devuelve todos los registros de la tabla, porque todos los rectángulos y los triángulos son polígonos. Active Record se encarga de regresar instancias de su clase correspondiente en el conjunto de resultados.

Los tipos se cargan automáticamente según sea necesario. Por ejemplo, si "Polygon.first" es un rectángulo y Rectangle aún no se ha cargado, Active Record lo carga automáticamente y el el registro está instanciado correctamente.

Todo bien, pero si en lugar de realizar consultas basadas en la clase raíz necesitamos para trabajar en alguna subclase, las cosas se ponen interesantes.

Mientras trabaja con Polygon, no es necesario que conozca todos sus descendientes, porque cualquier cosa en la tabla es por definición un polígono, pero cuando se trabaja con Las subclases Active Record deben poder enumerar los tipos que busca. para. Veamos un ejemplo.

Rectangle.all solo carga rectángulos agregando una restricción de tipo a la consulta:

SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle")

Introduzcamos ahora una subclase de Rectangle:

# app/models/square.rb
class Square < Rectangle
end

Rectangle.all ahora debería devolver rectángulos y cuadrados:

SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle", "Square")

Pero hay una advertencia aquí: ¿Cómo sabe Active Record que la clase Square existe en absoluto?

Incluso si el archivo app/models/square.rb existe y define la clase Square, si aún no se ha utilizado ningún código de esa clase, Rectangle.all emite la consulta

SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle")

Eso no es un error, la consulta incluye todos los descendientes * conocidos * de Rectangle.

Una forma de asegurarse de que esto funcione correctamente independientemente del orden de ejecución es cargue manualmente las subclases directas en la parte inferior del archivo que define cada clase intermedia:

# app/models/rectangle.rb
class Rectangle < Polygon
end
require_dependency 'square'

Esto debe suceder para cada clase intermedia (no raíz y no hoja). los la clase raíz no abarca la consulta por tipo y, por lo tanto, no necesariamente Hay que conocer a todos sus descendientes.

9.4 Autoloading and require

Los archivos que definen las constantes que se van a cargar automáticamente nunca deben ser required:

require "user" # DO NOT DO THIS

class UsersController < ApplicationController
  ...
end

Hay dos posibles errores aquí en el modo de desarrollo:

  1. Si User se carga automáticamente antes de llegar al require, app/models/user.rb se ejecuta de nuevo porque load no actualiza$ LOADED_FEATURES.

  2. Si el require se ejecuta primero, Rails no marca al User como autocargado constante y los cambios en app/models/user.rb no se vuelven a cargar.

Simplemente siga el flujo y use la carga automática constante siempre, nunca mezcle carga automática y require. Como último recurso, si algún archivo necesita absolutamente cargar un determinado archivo use require_dependency para jugar bien con constante carga automática. Sin embargo, esta opción rara vez se necesita en la práctica.

Por supuesto, el uso de require en archivos autocargados para cargar un tercero ordinario bibliotecas está bien, y Rails es capaz de distinguir sus constantes, son no marcado como autocargado.

9.5 Autoloading and Initializers

Considere esta asignación en config/initializers/set_auth_service.rb:

AUTH_SERVICE = if Rails.env.production?
  RealAuthService
else
  MockedAuthService
end

El propósito de esta configuración sería que la aplicación use la clase que corresponde al entorno a través de AUTH_SERVICE. En modo de desarrollo MockedAuthService se carga automáticamente cuando se ejecuta el inicializador. Supongamos hacemos algunas solicitudes, cambiamos su implementación y volvemos a activar la aplicación. Para nuestra sorpresa, los cambios no se reflejan. ¿Por qué?

Como vimos anteriormente, Rails elimina las constantes cargadas automáticamente, pero AUTH_SERVICE almacena el objeto de clase original. Rancio, no accesible utilizando la constante original, pero perfectamente funcional.

El siguiente código resume la situación:

class C
  def quack
    'quack!'
  end
end

X = C
Object.instance_eval { remove_const(:C) }
X.new.quack # => quack!
X.name      # => C
C           # => uninitialized constant C (NameError)

Por eso, no es una buena idea cargar automáticamente constantes en la aplicación inicialización.

En el caso anterior podríamos implementar un punto de acceso dinámico:

# app/models/auth_service.rb
class AuthService
  if Rails.env.production?
    def self.instance
      RealAuthService
    end
  else
    def self.instance
      MockedAuthService
    end
  end
end

and have the application use AuthService.instance instead. AuthService would be loaded on demand and be autoload-friendly.

9.6 require_dependency and Initializers

Como vimos antes, require_dependency carga archivos en un formato compatible con la carga automática. camino. Sin embargo, normalmente esta llamada no tiene sentido en un inicializador.

Se podría pensar en hacer algo de require_dependency llamadas en un inicializador para asegurarse de que ciertas constantes se cargan por adelantado, para ejemplo como un intento de abordar el problema con las ITS.

El problema es que en el modo de desarrollo las constantes cargadas automáticamente se borran si hay algún cambio relevante en el sistema de archivos. Si eso sucede entonces ¡Estamos en la misma situación que el inicializador quería evitar!

Las llamadas a require_dependency deben estar escritas estratégicamente en autocargado lugares.

9.7 When Constants aren't Missed

9.7.1 Relative References

Consideremos un simulador de vuelo. La aplicación tiene un modelo de vuelo predeterminado

# app/models/flight_model.rb
class FlightModel
end

que puede ser anulado por cada avión, por ejemplo

# app/models/bell_x1/flight_model.rb
module BellX1
  class FlightModel < FlightModel
  end
end

# app/models/bell_x1/aircraft.rb
module BellX1
  class Aircraft
    def initialize
      @flight_model = FlightModel.new
    end
  end
end

El inicializador quiere crear un BellX1::FlightModel y el anidamiento tiene "BellX1", se ve bien. Pero si se carga el modelo de vuelo predeterminado y el uno para el Bell-X1 no lo es, el intérprete puede resolver el nivel superior Por tanto, FlightModel y la carga automática no se activan paraBellX1::FlightModel.

Ese código depende de la ruta de ejecución.

Este tipo de ambigüedades a menudo se pueden resolver utilizando constantes calificadas:

module BellX1
  class Plane
    def flight_model
      @flight_model ||= BellX1::FlightModel.new
    end
  end
end

Además, require_dependency es una solución:

require_dependency 'bell_x1/flight_model'

module BellX1
  class Plane
    def flight_model
      @flight_model ||= FlightModel.new
    end
  end
end
9.7.2 Qualified References

Este problema solo es posible en Ruby <2.5.

Dado

# app/models/hotel.rb
class Hotel
end

# app/models/image.rb
class Image
end

# app/models/hotel/image.rb
class Hotel
  class Image < Image
  end
end

la expresión Hotel::Image es ambigua porque depende de la ejecución camino.

Como vimos antes, Ruby parece hasta la constante en Hotel y sus antepasados. Si app/models/image.rb tiene se ha cargado pero app/models/hotel/image.rb no lo ha hecho, Ruby no encuentra Image en Hotel, pero lo hace en Object:

$ bin/rails runner 'Image; p Hotel::Image' 2>/dev/null
Image # NOT Hotel::Image!

El código que evalúa Hotel::Image debe asegurarse app/models/hotel/image.rb se ha cargado, posiblemente con require_dependency.

En estos casos, el intérprete emite una advertencia:

warning: toplevel constant Image referenced by Hotel::Image

Esta sorprendente resolución constante se puede observar con cualquier clase clasificatoria:

2.1.5 :001 > String::Array
(irb):1: warning: toplevel constant Array referenced by String::Array
 => Array

Para encontrar este problema, el espacio de nombres calificado debe ser una clase, Object no es un antepasado de módulos.

9.8 Autoloading within Singleton Classes

Supongamos que tenemos estas definiciones de clase:

# app/models/hotel/services.rb
module Hotel
  class Services
  end
end

# app/models/hotel/geo_location.rb
module Hotel
  class GeoLocation
    class << self
      Services
    end
  end
end

Si Hotel::Services se conoce por el tiempo app/models/hotel/geo_location.rb se está cargando, "Servicios" es resuelto por Ruby porque "Hotel" pertenece al anidando cuando se abre la clase singleton de Hotel::GeoLocation.

Pero si no se conoce Hotel::Services, Rails no puede cargarlo automáticamente, la aplicación genera "NameError".

La razón es que se activa la carga automática para la clase singleton, que es anónimo, y como vimos antes, Rails solo verifica espacio de nombres de nivel superior en ese caso de borde.

Una solución fácil a esta advertencia es calificar la constante:

module Hotel
  class GeoLocation
    class << self
      Hotel::Services
    end
  end
end

9.9 Autoloading in BasicObject

Los descendientes directos de BasicObject no tienen Object entre sus antepasados y no puede resolver constantes de nivel superior:

class C < BasicObject
  String # NameError: uninitialized constant C::String
end

Cuando se trata de la carga automática, la trama tiene un giro. Consideremos:

class C < BasicObject
  def user
    User # WRONG
  end
end

Dado que Rails comprueba el espacio de nombres de nivel superior, User se carga automáticamente primera vez que se invoca el método user. Solo obtiene la excepción si el La constante User se conoce en ese punto, en particular en una * segunda * llamada a user:

c = C.new
c.user # surprisingly fine, User
c.user # NameError: uninitialized constant C::User

porque detecta que un espacio de nombres principal ya tiene la constante (consulte Qualified Referencias).

Al igual que con Ruby puro, dentro del cuerpo de un descendiente directo del uso de BasicObject siempre caminos constantes absolutos:

class C < BasicObject
  ::String # RIGHT

  def user
    ::User # RIGHT
  end
end

9.10 Autoloading in the Test Environment

Al configurar el entorno de "prueba" para la carga automática, puede considerar varios factores.

Por ejemplo, podría valer la pena ejecutar sus pruebas con una configuración idéntica a la de producción (config.eager_load = true, config.cache_classes = true) para detectar cualquier problema antes de que llegue a producción (esto es una compensación por la falta de paridad dev-prod). Sin embargo, esto ralentizará el tiempo de arranque para las pruebas individuales en una máquina de desarrollo (y no es inmediatamente compatible con Spring, ver más abajo). Así que una posibilidad es hacer esto en un CI solo máquina (que debería funcionar sin resorte).

En una máquina de desarrollo, puede ejecutar sus pruebas con lo que sea más rápido (idealmente config.eager_load = false).

Con el precargador de Spring (incluido con las nuevas aplicaciones de Rails), lo ideal es mantener config.eager_load = false según el desarrollo. A veces puede terminar con una configuración híbrida (config.eager_load = true,config.cache_classes = true Y config.enable_dependency_loading = true), consulte edición de primavera. Sin embargo, podría ser más sencillo mantener la misma configuración que el desarrollo y resolver lo que sea que esté causando que falle la carga automática (tal vez por los resultados de las pruebas de CI).

De vez en cuando puede necesitar explícitamente eager_load usando Rails .application.eager_load!en la configuración de sus pruebas; esto podría ocurrir si sus pruebas involucran subprocesos múltiples.

10 Troubleshooting

10.1 Tracing Autoloads

Active Support is able to report constants as they are autoloaded. To enable these traces in a Rails application, put the following two lines in some initializer:

ActiveSupport::Dependencies.logger = Rails.logger
ActiveSupport::Dependencies.verbose = true

10.2 Where is a Given Autoload Triggered?

Si se está cargando automáticamente Foo constante, y le gustaría saber de dónde viene esa carga automática, simplemente arroje

puts caller

en la parte superior de foo.rb e inspeccione el seguimiento de la pila impresa.

10.3 Which Constants Have Been Autoloaded?

En cualquier momento dado,

ActiveSupport::Dependencies.autoloaded_constants

tiene la colección de constantes que se han cargado automáticamente hasta ahora.

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.