Ruby: require vs require_relative - prática recomendada para a execução em execução no Ruby <1.9.2 e> = 1.9.2

153

Qual é a melhor prática se eu quiser requireum arquivo relativo no Ruby e trabalhar com o 1.8.xe> = 1.9.2?

Vejo algumas opções:

  • basta fazer $LOAD_PATH << '.'e esquecer tudo
  • Faz $LOAD_PATH << File.dirname(__FILE__)
  • require './path/to/file'
  • verifique se RUBY_VERSION<1.9.2 e defina require_relativecomo require, use em require_relativequalquer lugar onde for necessário posteriormente
  • verifique se require_relativejá existe, se existir, tente continuar como no caso anterior
  • use construções estranhas como - infelizmente elas não parecem funcionar completamente no Ruby 1.9, porque, por exemplo:
    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>'
  • Construção ainda mais estranha: parece funcionar, mas é estranha e não é muito bonita.
    require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')
  • Use a gem de backports - é meio pesada, requer infraestrutura de rubygems e inclui várias outras soluções alternativas, enquanto eu só quero requiretrabalhar com arquivos relativos.

Há uma pergunta intimamente relacionada no StackOverflow que fornece mais alguns exemplos, mas não fornece uma resposta clara - o que é uma prática recomendada.

Existe alguma solução universal decente e aceita por todos para fazer meu aplicativo rodar em Ruby <1.9.2 e> = 1.9.2?

ATUALIZAR

Esclarecimento: não quero apenas respostas como "você pode fazer X" - na verdade, eu já mencionei a maioria das opções em questão. Quero justificativa , ou seja, por que é uma prática recomendada, quais são seus prós e contras e por que deve ser escolhida entre as outras.

GreyCat
fonte
3
Oi eu sou novo. Alguém poderia explicar desde o início - qual é a diferença entre requiree require_relative?
Coronel Panic
3
No Ruby 1.8 antigo, se você executava um arquivo a.rbe desejava fazer o interpretador ler e analisar o conteúdo do arquivo b.rbno diretório atual (geralmente o mesmo diretório que o anterior a.rb), basta escrever require 'b'e seria bom, pois o caminho de pesquisa padrão incluía o diretório atual. No Ruby 1.9 mais moderno, você terá que escrever require_relative 'b'neste caso, pois require 'b'somente pesquisaria nos caminhos padrão da biblioteca. É isso que interrompe a compatibilidade com versões anteriores e posteriores de scripts mais simples que não serão instalados corretamente (por exemplo, eles próprios instalam scripts).
GreyCat 9/10/12
Agora você pode usar backportsapenas para require_relative, veja a minha resposta ...
Marc-André Lafortune

Respostas:

64

Uma solução alternativa para isso foi adicionada à gema 'aws', então pensei em compartilhar, porque foi inspirada neste post.

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

Isso permite que você use o require_relativeRuby 1.9.2 no Ruby 1.8 e 1.9.1.

Travis Reeder
fonte
3
Como você requer o arquivo require_relative.rb? Você precisa exigir require_relative.rb e, em seguida, require_relative o restante dos requisitos. Ou eu estou esquecendo de alguma coisa?
ethicalhack3r
7
A require_relativefunção está incluída em um projeto de extensão para as bibliotecas principais do Ruby, encontradas aqui: rubyforge.org/projects/extensions Você deve poder instalá-las gem install extensions. Então no seu código adicione a seguinte linha antes da require_relative: require 'extensões / all' (proveniente de post de Aurril aqui )
thegreendroid
O ethichack3r simplesmente copia e cola esse código na parte superior do seu script ruby ​​ou, se estiver nos trilhos, jogue-o no top environment.rb ou algo assim.
Travis Reeder #
46

Antes de pular para a versão 1.9.2, usei o seguinte para requerimentos relativos:

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

É um pouco estranho a primeira vez que você o vê, porque parece que há um '..' extra no início. O motivo é que expand_pathirá expandir um caminho relativo ao segundo argumento, e o segundo argumento será interpretado como se fosse um diretório. __FILE__obviamente não é um diretório, mas isso não importa, já expand_pathque não se importa se os arquivos existem ou não, apenas aplicarão algumas regras para expandir coisas como .., .e ~. Se você conseguir superar o "waitaminute" inicial, não há um extra ..lá? Eu acho que a linha acima funciona muito bem.

Supondo que __FILE__seja /absolute/path/to/file.rb, o que acontece é que expand_pathconstruirá a sequência /absolute/path/to/file.rb/../relative/pathe aplicará uma regra que diz que ..deve remover o componente do caminho antes ( file.rbneste caso), retornando /absolute/path/to/relative/path.

Essa é a melhor prática? Depende do que você quer dizer com isso, mas parece que está em toda a base de código do Rails, então eu diria que é pelo menos um idioma bastante comum.

Theo
fonte
1
Eu vejo isso comumente também. É feio, mas parece funcionar bem.
Yfeldblum
12
um pouco mais limpo: exigem File.expand_path ( 'caminho relativo /', File.dirname ( ARQUIVO ))
Yannick Wurm
1
Não acho que seja muito mais limpo, é só mais tempo. Ambos são fugazes como o inferno e, ao escolher entre duas opções ruins, prefiro a que exige menos digitação.
Theo
6
Parece que File.expand_path ('../ relpath.x', File.dirname ( FILE )) é um idioma melhor, embora seja mais detalhado. Confiar na funcionalidade indiscutivelmente quebrada de um caminho de arquivo que está sendo interpretado como um caminho de diretório com um diretório extra inexistente pode ser interrompido quando / se essa funcionalidade for corrigida.
jpgeek
1
Quebrado, talvez, mas sempre foi assim no UNIX. Não há diferença entre um diretório e um arquivo quando se trata de caminhos e a resolução de '..' - portanto, não estou perdendo tempo com isso.
Theo
6

A picareta tem um trecho para isso para 1,8. Aqui 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

Basicamente, apenas usa o que Theo respondeu, mas você ainda pode usá-lo require_relative.

Paul Hoffer
fonte
Como verificar se este trecho deve ser ativado ou não corretamente? Usando $RUBY_VERSIONou verificando se require_relativeexiste diretamente?
GreyCat 02/12/19
1
Sempre tipo pato, verifique se require_relativeestá definido.
Theo
@Theo @GreyCat sim, gostaria de verificar se é necessário. Eu estava apenas colocando o trecho aqui para as pessoas mostrarem. Pessoalmente, eu usaria a resposta de Greg de qualquer maneira, eu realmente estava postando isso porque alguém tinha mencionado isso sem tê-lo.
Paul Hoffer 12/12
6
$LOAD_PATH << '.'

$LOAD_PATH << File.dirname(__FILE__)

Não é um bom hábito de segurança: por que você deve expor todo o diretório?

require './path/to/file'

Isso não funciona se RUBY_VERSION <1.9.2

use construções estranhas como

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

Construção ainda mais estranha:

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

Use a gem de backports - é meio pesada, requer infraestrutura de rubygems e inclui várias outras soluções alternativas, enquanto eu só quero exigir que trabalhe com arquivos relativos.

Você já respondeu por que essas não são as melhores opções.

verifique se RUBY_VERSION <1.9.2, defina require_relative como require, use require_relative em todos os lugares onde for necessário posteriormente

verifique se require_relative já existe, se existir, tente continuar como no caso anterior

Isso pode funcionar, mas há uma maneira mais rápida e segura: lidar com a exceção 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
fonte
5

Sou fã do uso da gema rbx-require-related ( fonte ). Foi originalmente escrito para Rubinius, mas também suporta MRI 1.8.7 e não faz nada no 1.9.2. Exigir uma gema é simples, e não preciso adicionar trechos de código ao meu projeto.

Adicione-o ao seu Gemfile:

gem "rbx-require-relative"

Então require 'require_relative'antes de você require_relative.

Por exemplo, um dos meus arquivos de teste fica assim:

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

Essa é a solução mais limpa de qualquer uma dessas IMO, e a gema não é tão pesada quanto os backports.

Edward Anderson
fonte
4

A backportsgema agora permite o carregamento individual de backports.

Você poderia então simplesmente:

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

Isso requirenão afetará as versões mais recentes, nem atualizará outros métodos internos.

Marc-André Lafortune
fonte
3

Outra opção é informar ao intérprete quais caminhos procurar

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

Um problema que eu não vi apontado nas soluções baseadas em __FILE__ é que elas quebram com relação aos links simbólicos. Por exemplo, diga que tenho:

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

O script principal, o ponto de entrada, o aplicativo é foo.rb. Este arquivo está vinculado a ~ / Scripts / foo, que está no meu $ PATH. Esta declaração de exigência é interrompida quando executo 'foo':

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

Como __FILE__ é ~ / Scripts / foo, a instrução require acima procura por ~ / Scripts / foo / lib / someinclude.rb que obviamente não existe. A solução é simples. Se __FILE__ for um link simbólico, ele precisará ser desreferenciado. Nome do caminho # realpath nos ajudará com esta situação:

requer "nome do caminho"
requer File.join (File.dirname (Nome do caminho.new (__ FILE __). caminho real)), "lib / someinclude")
jptros
fonte
2

Se você estivesse construindo uma gema, não gostaria de poluir o caminho de carregamento.

Porém, no caso de um aplicativo independente, é muito conveniente adicionar o diretório atual ao caminho de carregamento, como nos dois primeiros exemplos.

Meu voto vai para a primeira opção na lista.

Gostaria muito de ver uma sólida literatura sobre boas práticas em Ruby.

Casey Watson
fonte
1
Re: "Gostaria muito de ver uma sólida literatura sobre boas práticas em Ruby". Você pode baixar Ruby Best Practices, de Gregory Brown . Você também pode conferir o site Rails Best Practices .
Michael Stalker
1

Eu definiria o meu próprio relative_requirese ele não existir (ou seja, sob 1,8) e depois usaria a mesma sintaxe em todos os lugares.

Phrogz
fonte
0

Ruby on Rails:

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