Ruby: требуется vs require_relative - наилучшая практика для работы в Ruby <1.9.2 и >= 1.9.2

Какова наилучшая практика, если я хочу require относительный файл в Ruby, и я хочу, чтобы он работал как в файлах 1.8.x и >= 1.9.2?

Я вижу несколько вариантов:

  • просто сделайте $LOAD_PATH << '.' и забудьте все
  • do $LOAD_PATH << File.dirname(__FILE__)
  • require './path/to/file'
  • проверьте, если RUBY_VERSION < 1.9.2, затем определите require_relative как require, используйте require_relative всюду, где это необходимо впоследствии
  • проверьте, существует ли require_relative, если это так, попробуйте продолжить, как в предыдущем случае
  • использовать такие странные конструкции, как
    require File.join(File.dirname(__FILE__), 'path/to/file')
    - увы, они, похоже, не работают в Ruby 1.9, потому что, например:
    $ 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>'
  • Даже более сложная конструкция:
    require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')
    , похоже, работает, но это странно и не очень хорошо выглядит.
  • Используйте backports gem - он тяжелый, он требует инфраструктуры rubygems и включает тонны других обходных решений, в то время как я просто хочу require для работы с относительными файлами.

Там qaru.site/info/32400/..., который дает еще несколько примеров, но он не дает четкого ответа - что является лучшей практикой.

Есть ли какое-либо достойное универсальное решение, принятое всеми, чтобы мое приложение выполнялось на Ruby < 1.9.2 и >= 1.9.2?

UPDATE

Уточнение: я не хочу просто ответов, таких как "вы можете сделать X" - на самом деле, я уже упомянул большинство вариантов, о которых идет речь. Я хочу обоснования, то есть, почему это лучшая практика, каковы ее плюсы и минусы и почему ее следует выбирать среди других.

Ответ 1

Обходной путь для этого был просто добавлен в жемчужину 'aws', поэтому я подумал, что буду делиться этим, поскольку он был вдохновлен этим сообщением.

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

Это позволяет использовать require_relative, как в рубине 1.9.2 в ruby ​​1.8 и 1.9.1.

Ответ 2

Прежде чем я сделал переход к 1.9.2, я использовал следующее для относительного:

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

Это немного странно в первый раз, когда вы его видите, потому что в начале кажется, что есть дополнительные "..". Причина в том, что expand_path будет расширять путь относительно второго аргумента, а второй аргумент будет интерпретироваться так, как если бы он был каталогом. __FILE__, очевидно, не является каталогом, но это не имеет значения, так как expand_path не имеет значения, существуют ли файлы или нет, он просто применит некоторые правила для расширения таких вещей, как .., . и ~. Если вы можете преодолеть первоначальный "waitaminute", там нет дополнительного ..? ". Я думаю, что линия выше работает достаточно хорошо.

Предполагая, что __FILE__ является /absolute/path/to/file.rb, происходит следующее: expand_path построит строку /absolute/path/to/file.rb/../relative/path, а затем применит правило, в котором говорится, что .. должен удалить перед ним компонент пути (file.rb в этом случае), возвращая /absolute/path/to/relative/path.

Это лучшая практика? В зависимости от того, что вы подразумеваете под этим, но это похоже на всю базу кода Rails, поэтому я бы сказал, что это, по крайней мере, достаточно распространенная идиома.

Ответ 3

В Pickaxe есть фрагмент для 1.8. Вот он:

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

В основном он использует то, что ответил Тео, но так что вы все еще можете использовать require_relative.

Ответ 4

$LOAD_PATH << '.'

$LOAD_PATH << File.dirname(__FILE__)

Это не хорошая привычка к безопасности: зачем вам раскрывать всю свою директорию?

require './path/to/file'

Это не работает, если RUBY_VERSION < 1.9.2

используйте странные конструкции, такие как

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

Даже более сложная конструкция:

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

Используйте backports gem - он тяжелый, он требует рубигем инфраструктуры и включает тонны других обходных решений, в то время как я просто требуется работать с относительными файлами.

Вы уже ответили, почему это не лучшие варианты.

проверьте, что RUBY_VERSION < 1.9.2, затем определите require_relative как требуют, используйте require_relative везде, где это необходимо впоследствии

проверьте, существует ли require_relative, если это так, попробуйте продолжить как в предыдущем случае

Это может работать, но там безопаснее и быстрее: для устранения исключения 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

Ответ 5

Я поклонник использования rbx-require-relative gem (источник). Он был первоначально написан для Rubinius, но он также поддерживает MRI 1.8.7 и ничего не делает в 1.9.2. Требование жемчужины прост, и мне не нужно бросать фрагменты кода в мой проект.

Добавьте его в свой Gemfile:

gem "rbx-require-relative"

Затем require 'require_relative' перед вами require_relative.

Например, один из моих тестовых файлов выглядит следующим образом:

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

Это самое чистое решение из любой из этих IMO, и драгоценный камень не так тяжел, как backports.

Ответ 6

backports gem теперь позволяет индивидуальную загрузку backports.

Вы могли бы просто:

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

Этот require не будет влиять на новые версии и не будет обновлять какие-либо другие встроенные методы.

Ответ 7

Другой вариант - указать интерпретатору, какие пути к поиску

ruby -I /path/to/my/project caller.rb

Ответ 8

Одна из проблем, которые я не видел с решениями, основанными на __FILE__, заключается в том, что они ломаются относительно символических ссылок. Например, у меня есть:

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

Основная script, точка входа, приложение - foo.rb. Этот файл связан с ~/Scripts/foo, который находится в моем $PATH. Это требование должно быть нарушено, когда я выполняю 'foo':

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

Потому что __FILE__ is ~/Scripts/foo, поэтому инструкция require ищет ~/Scripts/foo/lib/someinclude.rb, которая, очевидно, не существует. Решение прост. Если __FILE__ - это символическая ссылка, она должна быть разыменована. Путь # realpath поможет нам в этой ситуации:

require "pathname"
require File.join(File.dirname(Pathname.new(__FILE__).realpath), "lib/someinclude")

Ответ 9

Если вы строили драгоценный камень, вы не захотите загрязнять путь загрузки.

Но, в случае автономного приложения очень удобно просто добавить текущий каталог к ​​пути загрузки, как в первых двух примерах.

Мое голосование переходит к первому варианту в списке.

Мне бы хотелось увидеть какую-нибудь солидную рубиновскую литературу.

Ответ 10

Я бы определил свой собственный relative_require, если он не существует (т.е. под 1.8), а затем везде использовать тот же синтаксис.

Ответ 11

Ruby on Rails:

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