Ruby: require vs require_relative - рекомендуется использовать обходные пути для Ruby <1.9.2 и> = 1.9.2

153

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

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

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

В StackOverflow есть тесно связанный вопрос, который дает еще несколько примеров, но он не дает четкого ответа - что является лучшей практикой.

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

ОБНОВИТЬ

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

GreyCat
источник
3
Привет я новенький Может ли кто-нибудь объяснить с самого начала - в чем разница между requireи require_relative?
полковник Паник
3
В более старом Ruby 1.8, если вы запускали файл a.rbи хотели, чтобы интерпретатор считывал и анализировал содержимое файла b.rbв текущем каталоге (обычно тот же каталог, что и в a.rb), вы просто пишете, require 'b'и это будет хорошо, поскольку путь поиска по умолчанию включает текущий каталог. В более современном Ruby 1.9 вам придется писать require_relative 'b'в этом случае, как если require 'b'бы вы искали только стандартные пути к библиотекам. Это то, что нарушает прямую и обратную совместимость для простых сценариев, которые не будут установлены должным образом (например, сами сценарии установки ).
GreyCat
Теперь вы можете использовать backportsтолько для require_relative, см. Мой ответ ...
Марк-Андре Lafortune

Ответы:

64

Обходной путь для этого был только что добавлен к жемчужине '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как в ruby ​​1.9.2 в ruby ​​1.8 и 1.9.1.

Трэвис Ридер
источник
3
Как вам требуется файл require_relative.rb? Вы должны потребовать require_relative.rb, а затем require_relative остальные требования. Или я что-то упустил?
ethicalhack3r
7
Эта require_relativeфункция включена в проект расширения базовых библиотек Ruby, который можно найти здесь: rubyforge.org/projects/extensions. У вас должна быть возможность установить их gem install extensions. Затем в вашем коде добавьте следующую строку перед require_relative: require 'extensions / all' (взято из поста Aurril здесь )
thegreendroid
@ ethicalhack3r просто скопируйте и вставьте этот код в верхнюю часть вашего сценария ruby ​​или, если он используется в rails, добавьте его в верхний файл environment.rb или что-то в этом роде.
Тревис Ридер
46

Прежде чем перейти к 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, поэтому я бы сказал, что это, по крайней мере, достаточно распространенная идиома.

Тео
источник
1
Я тоже часто это вижу. Это некрасиво, но, похоже, хорошо работает.
yfeldblum
12
немного чище: требуется File.expand_path ('относительный / путь', File.dirname ( FILE ))
Янник Вурм
1
Я не думаю, что это намного чище, просто дольше. Они оба ужасны, и при выборе между двумя плохими вариантами я предпочитаю тот, который требует меньше печатания.
Тео
6
Кажется, что File.expand_path ('../ relpath.x', File.dirname ( FILE )) - лучшая идиома, хотя и более многословная. Если полагаться на нарушенную функциональность пути к файлу, который интерпретируется как путь к каталогу с дополнительным несуществующим каталогом, он может сломаться, если / если эта функциональность исправлена.
jpgeek
1
Возможно, он сломан, но в UNIX так было всегда. Просто нет никакой разницы между каталогом и файлом, когда речь идет о путях и разрешении '..' - поэтому я не теряю сна из-за этого.
Тео
6

У Кирки есть фрагмент для этого для 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.

Пол Хоффер
источник
Как проверить, должен ли этот фрагмент быть активирован или не правильно? Используя $RUBY_VERSIONили проверяя, require_relativeсуществует ли напрямую?
GreyCat
1
Всегда тип утки, проверьте, если require_relativeопределено.
Тео
@Theo @GreyCat Да, я бы проверил, нужно ли это. Я просто помещал фрагмент, чтобы люди могли его показать. Лично я в любом случае использовал бы ответ Грега, я действительно просто отправлял это, потому что кто-то упомянул это, не имея его самостоятельно.
Пол Хоффер
6
$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 - он довольно тяжелый, требует инфраструктуры rubygems и включает в себя множество других обходных путей, а я просто хочу работать с относительными файлами.

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

проверьте, если RUBY_VERSION <1.9.2, затем определите require_relative как require, используйте 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 -lative ( источник ). Первоначально он был написан для 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, и камень не такой тяжелый, как бэкпорт.

Эдвард Андерсон
источник
4

backportsКамень теперь позволяет загружать отдельные Backports.

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

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

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

Марк-Андре Лафортун
источник
3

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

ruby -I /path/to/my/project caller.rb
eradman
источник
3

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

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

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

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

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

требует "путь"
require File.join (File.dirname (Pathname.new (__ FILE __). realpath), "lib / someinclude")
jptros
источник
2

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

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

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

Я хотел бы видеть некоторую основательную литературу по лучшим практикам Ruby.

Кейси Уотсон
источник
1
Re: «Я хотел бы видеть некоторую основательную литературу по лучшим практикам Ruby». Вы можете скачать Ruby Best Practices Грегори Брауна . Вы также можете ознакомиться с сайтом Rails Best Practices .
Майкл Сталкер
1

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

Phrogz
источник
0

Ruby on Rails способом:

config_path = File.expand_path("../config.yml", __FILE__)
Vaibhav
источник