Ruby: вимагати vs requ_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" - адже я вже згадував більшість питань, про які йдеться. Я хочу обґрунтувати , тобто чому це найкраща практика, які її плюси та мінуси та чому її слід обирати серед інших.


3
Привіт, я новачок. Чи може хтось пояснити з самого початку - в чому різниця між requireі require_relative?
Полковник Паніка

3
У старшій Ruby 1.8, якщо ви запустили файл a.rbі хотіли зробити так, щоб інтерпретатор читав і розбирав вміст файлу b.rbв поточному каталозі (як правило, той же самий dir, що і з a.rb), ви просто напишете, require 'b'і це буде добре, оскільки поточний каталог пошуку за замовчуванням включає поточний каталог. У більш сучасному Ruby 1.9 require_relative 'b'в цьому випадку вам доведеться писати так, як require 'b'шукати тільки в стандартних бібліотечних контурах. Це справа в тому, що такий вид перерви сумісності вперед і назад для більш простих сценаріїв, які не будуть встановлені належним чином (наприклад, встановлення самих скриптів).
GreyCat

Тепер ви можете використовувати backportsлише для require_relative, дивіться мою відповідь ...
Marc-André 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як у рубіні 1.9.2 у рубінах 1.8 та 1.9.1.


3
Як вам потрібен файл Requ_relative.rb? Вам потрібно вимагати Requ_relative.rb, а потім вимагати_relactive решта потреб. Або я щось пропускаю?
ethicalhack3r

7
require_relativeФункція включена в розширення проекту в основні бібліотеки Ruby, але і тут: rubyforge.org/projects/extensions Ви повинні бути в змозі встановити їх gem install extensions. Тоді до свого коду додайте наступний рядок перед require_relativeзапитом: вимагати "розширення / усі" (отримано з публікації Aurril тут )
thegreendroid

@ ethicalhack3r просто скопіюйте та вставте цей код у верхній частині вашого сценарію з рубіном або, якщо він знаходиться в рейках, закиньте його у верхнє оточення.rb чи щось.
Тревіс Редер

46

Перед тим, як здійснити стрибок до 1.9.2, я використовував наступні для відносних потреб:

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

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

Якщо припустити , що __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 ('родич / шлях', ім'я файлу (ім'я файлу ))
Yannick Wurm

1
Я не думаю, що це набагато чистіше, це просто довше. Вони обидва хитрі, як пекло, і, вибираючи між двома поганими варіантами, я віддаю перевагу тому, що потребує меншої кількості тексту.
Тео

6
Здається, що File.expand_path ('../ relpath.x', File.dirname ( FILE )) є кращою ідіомою, хоча вона є більш багатослівною. Опираючись на, можливо, порушену функціональність шляху до файлу, який інтерпретується як шлях до каталогу з додатковою неіснуючою каталогом, може порушитися, коли / якщо ця функція буде виправлена.
jpgeek

1
Можливо, зламано, але так було назавжди в UNIX. Тут просто немає різниці між каталогом і файлом, якщо мова йде про шляхи та роздільну здатність ".." - тому я не втрачаю сну над ним.
Тео

6

У 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.


Як перевірити, чи повинен цей фрагмент активовано чи не правильно? Використання $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')

Використовуйте дорогоцінний камінь, що підтримує резерви - це дуже важко, він вимагає інфраструктури рубігем і включає в себе безліч інших обхідних шляхів, тоді як мені просто потрібно вимагати роботи з відносними файлами.

Ви вже відповіли, чому це не найкращі варіанти.

перевірте, чи RUBY_VERSION <1.9.2, а потім визначте Requ_relative як вимагайте, використовуйте Requ_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-demand ( джерело) ). Спочатку він був написаний для Рубінія, але він також підтримує МРТ 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'

Це найчистіше рішення з будь-якого з цих ІМО, і дорогоцінний камінь не такий важкий, як підпорядкування.


4

Зараз 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

3

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

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

Основний сценарій, точка входу, додаток foo.rb. Цей файл пов'язаний з ~ / Scripts / foo, який знаходиться в моєму $ PATH. Цей запит на вимогу порушується, коли я виконую "foo":

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

Оскільки __FILE__ є ~ / Scripts / foo, тому заява на вимогу вище шукає ~ / Scripts / foo / lib / someinclude.rb, якого, очевидно, не існує. Рішення просте. Якщо __FILE__ є символічним посиланням, його потрібно знеструмити. Pathname # realpath допоможе нам у цій ситуації:

вимагати "ім'я шляху"
вимагають File.join (File.dirname (Pathname.new (__ FILE __). realpath), "lib / someinclude")

2

Якби ви будували дорогоцінний камінь, ви б не хотіли забруднити шлях навантаження.

Але у випадку окремої програми дуже зручно просто додати поточний каталог до шляху завантаження, як це робиться у перших двох прикладах.

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

Мені б хотілося побачити добру літературу з найкращих практик Рубі.


1
Re: "Мені б хотілося побачити добру літературу з найкращих практик Рубі". Ви можете завантажити найкращі практики Ruby Gregory Brown's . Ви також можете переглянути сайт Rails Best Practices .
Майкл Сталкер

1

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


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.