Agregar un directorio a $ LOAD_PATH (Ruby)

95

He visto dos técnicas de uso común para agregar el directorio del archivo que se está ejecutando actualmente en $ LOAD_PATH (o $ :). Veo las ventajas de hacer esto en caso de que no esté trabajando con una gema. Uno parece más detallado que el otro, obviamente, pero ¿hay alguna razón para ir con uno sobre el otro?

El primer método detallado (podría ser excesivo):

$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))

y el más sencillo, rápido y sucio:

$:.unshift File.dirname(__FILE__)

¿Alguna razón para ir con uno sobre el otro?

Mark W
fuente
2
Una versión un poco menos detallada de la detallada es:File.expand_path(File.dirname(__FILE__)).tap {|pwd| $LOAD_PATH.unshift(pwd) unless $LOAD_PATH.include?(pwd)}
Nathan Long
¿Qué tal la cláusula "a menos que"? ¿Cómo pueden ser equivalentes los dos anteriores?
inger
Como alguien que vino aquí para tratar de entender cómo usar esto, es súper críptico. No veo de dónde viene el nombre del directorio en los ejemplos. Agradecería que alguien pudiera aclarar esto.
SlySherZ
1
El uso __dir__(a partir de Ruby 2.0) puede hacer que cualquiera de estos sea más conciso.
Nathan Long

Respuestas:

50

Yo diría que vaya con $:.unshift File.dirname(__FILE__)el otro, simplemente porque he visto mucho más uso en el código que el anterior $LOAD_PATH, ¡y también es más corto!

Ryan Bigg
fuente
Cuando comencé con Ruby, obviamente pensé que $ LOAD_PATH era mejor. Pero una vez que se haya graduado del estado de principiante, solo usaría $ LOAD_PATH si estuviera tratando de hacer que mi código sea más legible para un principiante. Meh es una compensación. Depende de cuán "público" sea el código, siempre que el uso de memoria sea el mismo para cada uno, lo que supongo que es esencialmente.
boulder_ruby
9
Depende de la guía de estilo que sigas para tu proyecto. La popular Ruby Style Guide dice: "Evite el uso de variables especiales de estilo Perl (como $ :, $ ;, etc.). Son bastante crípticas y se desaconseja su uso en cualquier cosa que no sea scripts de una sola línea".
bobmagoo
152

La ruta de carga de Ruby se ve comúnmente escrita como $:, pero solo porque es corta, no la mejora. Si prefiere la claridad a la inteligencia, o si la brevedad en sí misma le da comezón, no necesita hacerlo solo porque todos los demás lo están. Saludar a ...

$LOAD_PATH

... y decir adiós a ...

# I don't quite understand what this is doing...
$:

fuente
29
Además, es mucho más difícil de Google para cadenas como "$:" que contienen solo símbolos.
DSimon
23

No me gusta mucho el camino "rápido y sucio". Cualquiera que sea nuevo en Ruby estará reflexionando sobre lo que $:.es.

Encuentro esto más obvio.

libdir = File.dirname(__FILE__)
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)

O si me importa tener el camino completo ...

libdir = File.expand_path(File.dirname(__FILE__))
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)

ACTUALIZACIÓN 2009/09/10

Últimamente he estado haciendo lo siguiente:

$:.unshift(File.expand_path(File.dirname(__FILE__))) unless
    $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))

Lo he visto en un montón de proyectos ruby ​​diferentes mientras navegaba por GitHub.

¿Parece ser la convención?

Luke Antins
fuente
@LukeAntins, esto es realmente genial, pero ¿dónde debería "arrancar" load_path en la aplicación?
gaussblurinc
@gaussblurinc En algún lugar 'cerca de la parte superior' de su lib / aplicación, pero realmente depende. Si tuvieras un binarchivo que siempre fue relativo al tuyo codey solo fue ejecutado por el binarchivo ... bootstrap en el contenedor. Si tiene una biblioteca, inicie el arranque en la parte superior del código de su biblioteca lib/code.rbpara obtener acceso a todo lo que se encuentra debajo lib/code/. ¡Espero que este paseo te ayude!
Luke Antins
1
RuboCop me informa que __dir__se puede usar para obtener una ruta al directorio del archivo actual.
Raphael
8

Si escribe script/consolesu proyecto Rails e ingresa $:, obtendrá una matriz que incluye todos los directorios necesarios para cargar Ruby. La conclusión de este pequeño ejercicio es que $:es una matriz. Siendo así, puede realizar funciones en él como anteponer otros directorios con el unshiftmétodo o el <<operador. Como lo insinuó en su declaración $:y $LOAD_PATHes lo mismo.

La desventaja de hacerlo de la manera rápida y sucia como mencionaste es esta: si ya tienes el directorio en tu ruta de arranque, se repetirá.

Ejemplo:

Tengo un complemento que creé llamado todo. Mi directorio está estructurado así:

/---vendedor
  |
  | --- / complementos
        |
        | --- / todo
              |
              | --- / lib
                    |
                    | --- / aplicación
                          |
                          | --- / modelos
                          | --- / controladores
              |
              | --- / rieles
                    |
                    | --- init.rb

En el archivo init.rb ingresé el siguiente código:

## In vendor/plugins/todo/rails/init.rb
    %w{ models controllers models }.each do |dir|
      path = File.expand_path(File.join(File.dirname(__FILE__), '../lib', 'app', dir))
      $LOAD_PATH << path
      ActiveSupport::Dependencies.load_paths << path
      ActiveSupport::Dependencies.load_once_paths.delete(path)
    end 

Observe cómo le digo al bloque de código que realice las acciones dentro del bloque para las cadenas 'modelos', 'controladores' y 'modelos', donde repito 'modelos'. (Para su información, %w{ ... }es solo otra forma de decirle a Ruby que contenga una matriz de cadenas). Cuando corro script/console, escribo lo siguiente:

>> puts $:

Y escribo esto para que sea más fácil leer el contenido de la cadena. La salida que obtengo es:

...
...
./Users/Me/mySites/myRailsApp/vendor/plugins/todo/lib/app/models
./Users/Me/mySites/myRailsApp/vendor/plugins/todo/lib/app/controllers
./Users/Me/mySites/myRailsApp/vendor/plugins/todo/lib/app/models

Como puede ver, aunque este es un ejemplo tan simple que podría crear mientras uso un proyecto en el que estoy trabajando actualmente, si no tiene cuidado, la forma rápida y sucia conducirá a rutas repetidas. El camino más largo buscará rutas repetidas y se asegurará de que no ocurran.

Si es un programador experimentado de Rails, probablemente tenga una muy buena idea de lo que está haciendo y probablemente no cometa el error de repetir rutas. Si eres un novato, optaría por el camino más largo hasta que entiendas realmente lo que estás haciendo.

Dyba
fuente
su respuesta muy útil y también bien explicada. Edición sugerida: el método load_pathsy load_once_paths.deletehan quedado obsoletos. Sería ActiveSupport::Dependencies.autoload_paths << path ActiveSupport::Dependencies.autoload_once_paths.delete(path)
útil
8

Lo mejor que he encontrado para agregar un directorio a través de una ruta relativa al usar Rspec. Lo encuentro lo suficientemente detallado, pero también un buen trazador de líneas.

$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
Dave Robertson
fuente
-2

Sé que ha pasado mucho tiempo desde que se hizo esta pregunta por primera vez, pero tengo una respuesta adicional que quiero compartir.

Tengo varias aplicaciones Ruby que fueron desarrolladas por otro programador durante varios años, y reutilizan las mismas clases en las diferentes aplicaciones aunque pueden acceder a la misma base de datos. Dado que esto viola la regla DRY, decidí crear una biblioteca de clases para ser compartida por todas las aplicaciones de Ruby. Podría haberlo puesto en la biblioteca principal de Ruby, pero eso ocultaría el código personalizado en la base de código común que no quería hacer.

Tuve un problema donde tuve un conflicto de nombre entre un nombre ya definido "profile.rb" y una clase que estaba usando. Este conflicto no fue un problema hasta que intenté crear la biblioteca de código común. Normalmente, Ruby busca primero las ubicaciones de las aplicaciones y luego va a las ubicaciones $ LOAD_PATH.

Application_controller.rb no pudo encontrar la clase que creé y arrojó un error en la definición original porque no es una clase. Desde que eliminé la definición de clase de la sección de aplicación / modelos de la aplicación, Ruby no pudo encontrarla allí y fue a buscarla en las rutas de Ruby.

Entonces, modifiqué la variable $ LOAD_PATH para incluir una ruta al directorio de la biblioteca que estaba usando. Esto se puede hacer en el archivo environment.rb en el momento de la inicialización.

Incluso con el nuevo directorio agregado a la ruta de búsqueda, Ruby arrojaba un error porque preferentemente tomaba primero el archivo definido por el sistema. La ruta de búsqueda en la variable $ LOAD_PATH busca preferentemente las rutas de Ruby primero.

Entonces, necesitaba cambiar el orden de búsqueda para que Ruby encontrara la clase en mi biblioteca común antes de buscar en las bibliotecas integradas.

Este código lo hizo en el archivo environment.rb:

Rails::Initializer.run do |config|

* * * * *

path = []
path.concat($LOAD_PATH)
$LOAD_PATH.clear
$LOAD_PATH << 'C:\web\common\lib'
$LOAD_PATH << 'C:\web\common'
$LOAD_PATH.concat(path)

* * * * *

end

No creo que pueda usar ninguna de las construcciones de codificación avanzadas proporcionadas antes en este nivel, pero funciona bien si desea configurar algo en el momento de la inicialización en su aplicación. Debe mantener el orden original de la variable $ LOAD_PATH original cuando se vuelve a agregar a la nueva variable; de ​​lo contrario, algunas de las clases principales de Ruby se perderán.

En el archivo application_controller.rb, simplemente uso un

require 'profile'
require 'etc' #etc

y esto carga los archivos de biblioteca personalizados para toda la aplicación, es decir, no tengo que usar comandos require en cada controlador.

Para mí, esta era la solución que estaba buscando y pensé en agregarla a esta respuesta para transmitir la información.

Timothy Dooling
fuente