Ruby: require vs require_relative: práctica recomendada para solucionar el problema en Ruby <1.9.2 y> = 1.9.2

153

¿Cuál es la mejor práctica si quiero requireun archivo relativo en Ruby y quiero que funcione tanto en 1.8.xy> = 1.9.2?

Veo algunas opciones:

  • solo hazlo $LOAD_PATH << '.'y olvídalo todo
  • hacer $LOAD_PATH << File.dirname(__FILE__)
  • require './path/to/file'
  • verifique si RUBY_VERSION<1.9.2, luego defina require_relativecomo require, use en require_relativetodas partes donde sea necesario después
  • compruebe si require_relativeya existe, si es así, intente continuar como en el caso anterior
  • use construcciones extrañas como , por desgracia, no parecen funcionar completamente en Ruby 1.9 porque, por ejemplo:
    require File.join(File.dirname(__FILE__), 'path/to/file')
    $ cat caller.rb
    require File.join(File.dirname(__FILE__), 'path/to/file')
    $ cat path/to/file.rb
    puts 'Some testing'
    $ ruby caller
    Some testing
    $ pwd
    /tmp
    $ ruby /tmp/caller
    Some testing
    $ ruby tmp/caller
    tmp/caller.rb:1:in 'require': no such file to load -- tmp/path/to/file (LoadError)
        from tmp/caller.rb:1:in '<main>'
  • Construcción aún más extraña: parece funcionar, pero es raro y no del todo bien parecido.
    require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')
  • Use gemas de backports : es un poco pesado, requiere infraestructura de rubygems e incluye toneladas de otras soluciones, mientras que solo quiero requiretrabajar con archivos relativos.

Hay una pregunta estrechamente relacionada en StackOverflow que da algunos ejemplos más, pero no da una respuesta clara, que es una práctica recomendada.

¿Existe alguna solución universal aceptable y aceptada por todos para hacer que mi aplicación se ejecute en Ruby <1.9.2 y> = 1.9.2?

ACTUALIZAR

Aclaración: no quiero solo respuestas como "puedes hacer X"; de hecho, ya he mencionado la mayoría de las opciones en cuestión. Quiero justificación , es decir, por qué es una mejor práctica, cuáles son sus pros y sus contras y por qué debería elegirse entre los demás.

Gato gris
fuente
3
Hola soy nuevo ¿Podría alguien explicar desde el principio cuál es la diferencia entre requirey require_relative?
Coronel Panic
3
En Ruby 1.8 anterior, si ejecutaba el archivo a.rby deseaba que el intérprete leyera y analizara el contenido del archivo b.rben el directorio actual (generalmente el mismo directorio que con a.rb), simplemente escribiría require 'b'y estaría bien, ya que la ruta de búsqueda predeterminada incluía el directorio actual. En Ruby 1.9 más moderno, tendrá que escribir require_relative 'b'en este caso, ya require 'b'que solo buscaría en rutas de biblioteca estándar. Eso es lo que rompe la compatibilidad hacia adelante y hacia atrás para secuencias de comandos más simples que no se instalarán correctamente (por ejemplo, instalen las secuencias de comandos ellos mismos).
GreyCat
Ahora puede usar backportssolo para require_relative, vea mi respuesta ...
Marc-André Lafortune

Respuestas:

64

Se acaba de agregar una solución alternativa a la gema 'aws', así que pensé en compartirla, ya que se inspiró en esta publicación.

https://github.com/appoxy/aws/blob/master/lib/awsbase/require_relative.rb

unless Kernel.respond_to?(:require_relative)
  module Kernel
    def require_relative(path)
      require File.join(File.dirname(caller[0]), path.to_str)
    end
  end
end

Esto le permite usar require_relativecomo lo haría en ruby ​​1.9.2 en ruby ​​1.8 y 1.9.1.

Travis Reeder
fuente
3
¿Cómo se requiere el archivo require_relative.rb? Debe requerir require_relative.rb y luego require_relative el resto de los require. ¿O me estoy perdiendo algo?
ethichack3r
77
La require_relativefunción se incluye en un proyecto de extensión de las bibliotecas principales de Ruby, que se encuentra aquí: rubyforge.org/projects/extensions . Debería poder instalarlas con gem install extensions. Luego, en su código, agregue la siguiente línea antes de require_relative: require 'extensiones / all' (procedente de la publicación de Aurril aquí )
thegreendroid
@ ethichack3r simplemente copie y pegue ese código en la parte superior de su script de ruby ​​o si está en rieles, tírelo en la parte superior environment.rb o algo así.
Travis Reeder
46

Antes de dar el salto a 1.9.2 usé lo siguiente para los requisitos relativos:

require File.expand_path('../relative/path', __FILE__)

Es un poco extraño la primera vez que lo ves, porque parece que hay un '...' extra al comienzo. La razón es que expand_pathexpandirá una ruta relativa al segundo argumento, y el segundo argumento se interpretará como si fuera un directorio. __FILE__obviamente no es un directorio, pero eso no importa ya expand_pathque no importa si los archivos existen o no, solo aplicará algunas reglas para expandir cosas como .., .y ~. Si puede superar el "valor de espera inicial, ¿no hay un extra ..allí?" Creo que la línea de arriba funciona bastante bien.

Suponiendo que __FILE__es así /absolute/path/to/file.rb, lo que sucede es que expand_pathconstruirá la cadena /absolute/path/to/file.rb/../relative/pathy luego aplicará una regla que dice que ..debería eliminar el componente de ruta antes ( file.rben este caso), regresando /absolute/path/to/relative/path.

¿Es esta la mejor práctica? Depende de lo que quieras decir con eso, pero parece que está en toda la base de código de Rails, por lo que diría que es al menos un idioma bastante común.

Theo
fuente
1
Veo esto comúnmente también. Es feo, pero parece funcionar bien.
yfeldblum
12
un poco más limpio: requiere File.expand_path ('relative / path', File.dirname ( FILE ))
Yannick Wurm
1
No creo que sea mucho más limpio, solo es más largo. Ambos son fugitivos como el infierno, y al elegir entre dos malas opciones, prefiero la que requiere menos tipeo.
Theo
66
Parece que File.expand_path ('../ relpath.x', File.dirname ( FILE )) es un mejor modismo, aunque es más detallado. Confiar en la funcionalidad posiblemente rota de una ruta de archivo que se interpreta como una ruta de directorio con un directorio adicional inexistente podría romperse cuando / si esa funcionalidad es fija.
jpgeek
1
Roto, tal vez, pero ha sido así para siempre en UNIX. Simplemente no hay diferencia entre un directorio y un archivo cuando se trata de rutas y la resolución de '...', por lo que no estoy perdiendo el sueño.
Theo
6

El pico tiene un fragmento para esto para 1.8. Aquí está:

def require_relative(relative_feature)
  c = caller.first
  fail "Can't parse #{c}" unless c.rindex(/:\d+(:in `.*')?$/)
  file = $`
  if /\A\((.*)\)/ =~ file # eval, etc.
    raise LoadError, "require_relative is called in #{$1}"
  end
  absolute = File.expand_path(relative_feature, File.dirname(file))
  require absolute
end

Básicamente solo usa lo que Theo respondió, pero aún así puedes usarlo require_relative.

Paul Hoffer
fuente
¿Cómo verificar si este fragmento se debe activar o no correctamente? ¿Usando $RUBY_VERSIONo comprobando si require_relativeexiste directamente?
GreyCat
1
Siempre tipo pato, verifique si require_relativeestá definido.
Theo
@Theo @GreyCat sí, comprobaría si es necesario. Estaba poniendo el fragmento aquí para que la gente lo muestre. Personalmente, utilizo la respuesta de Greg de todos modos, realmente estaba publicando esto porque alguien lo había mencionado sin tenerlo ellos mismos.
Paul Hoffer
6
$LOAD_PATH << '.'

$LOAD_PATH << File.dirname(__FILE__)

No es un buen hábito de seguridad: ¿por qué debería exponer todo su directorio?

require './path/to/file'

Esto no funciona si RUBY_VERSION <1.9.2

usar construcciones extrañas como

require File.join(File.dirname(__FILE__), 'path/to/file')

Construcción aún más extraña:

require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')

Use gemas de backports: es un poco pesado, requiere infraestructura de rubygems e incluye toneladas de otras soluciones, mientras que solo quiero requerir trabajar con archivos relativos.

Ya ha respondido por qué estas no son las mejores opciones.

compruebe si RUBY_VERSION <1.9.2, luego defina require_relative como require, use require_relative en todas partes donde sea necesario

verifique si require_relative ya existe, si es así, intente continuar como en el caso anterior

Esto puede funcionar, pero hay una forma más segura y rápida: lidiar con la excepción LoadError:

begin
  # require statements for 1.9.2 and above, such as:
  require "./path/to/file"
  # or
  require_local "path/to/file"
rescue LoadError
  # require statements other versions:
  require "path/to/file"
end
Claudio Floreani
fuente
5

Soy fanático de usar la gema ( fuente ) rbx-require-relative . Originalmente fue escrito para Rubinius, pero también es compatible con MRI 1.8.7 y no hace nada en 1.9.2. Requerir una gema es simple, y no tengo que tirar fragmentos de código en mi proyecto.

Agréguelo a su Gemfile:

gem "rbx-require-relative"

Entonces require 'require_relative'ante ti require_relative.

Por ejemplo, uno de mis archivos de prueba se ve así:

require 'rubygems'
require 'bundler/setup'
require 'minitest/autorun'
require 'require_relative'
require_relative '../lib/foo'

Esta es la solución más limpia de cualquiera de estos IMO, y la gema no es tan pesada como los backports.

Edward Anderson
fuente
4

La backportsgema ahora permite la carga individual de backports.

Entonces podrías simplemente:

require 'backports/1.9.1/kernel/require_relative'
# => Now require_relative works for all versions of Ruby

Esto requireno afectará a las versiones más nuevas, ni actualizará ningún otro método incorporado.

Marc-André Lafortune
fuente
3

Otra opción es decirle al intérprete qué rutas buscar

ruby -I /path/to/my/project caller.rb
eradman
fuente
3

Un problema que no he visto señalado con las soluciones basadas en __FILE__ es que se rompen con respecto a los enlaces simbólicos. Por ejemplo, decir que tengo:

~/Projects/MyProject/foo.rb
~/Projects/MyProject/lib/someinclude.rb

El script principal, el punto de entrada, la aplicación es foo.rb. Este archivo está vinculado a ~ / Scripts / foo que está en mi $ PATH. Esta declaración de requerimiento se rompe cuando ejecuto 'foo':

require File.join(File.dirname(__FILE__), "lib/someinclude")

Debido a que __FILE__ es ~ / Scripts / foo, la declaración require anterior busca ~ / Scripts / foo / lib / someinclude.rb que obviamente no existe. La solución es simple. Si __FILE__ es un enlace simbólico, debe ser desreferenciado. Pathname # realpath nos ayudará con esta situación:

requiere "ruta"
require File.join (File.dirname (Pathname.new (__ FILE __). realpath), "lib / someinclude")
jptros
fuente
2

Si estuviera construyendo una gema, no querría contaminar la ruta de carga.

Pero, en el caso de una aplicación independiente, es muy conveniente simplemente agregar el directorio actual a la ruta de carga como lo hace en los primeros 2 ejemplos.

Mi voto va a la primera opción en la lista.

Me encantaría ver un poco de literatura sólida sobre las mejores prácticas de Ruby.

Casey Watson
fuente
1
Re: "Me encantaría ver literatura sólida sobre las mejores prácticas de Ruby". Puede descargar Ruby Best Practices de Gregory Brown . También puede consultar el sitio de prácticas recomendadas de Rails .
Michael Stalker
1

Definiría el mío relative_requiresi no existe (es decir, bajo 1.8) y luego usaría la misma sintaxis en todas partes.

Phrogz
fuente
0

Ruby on Rails:

config_path = File.expand_path("../config.yml", __FILE__)
Vaibhav
fuente