Ruby: ต้องการ 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')
  • ใช้backport gem - มันหนักมากมันต้องใช้โครงสร้างพื้นฐาน rubygems และมีวิธีแก้ไขอื่น ๆ มากมายในขณะที่ฉันต้องการrequireทำงานกับไฟล์ที่เกี่ยวข้อง

มีคำถามที่เกี่ยวข้องอย่างใกล้ชิดที่ StackOverflowซึ่งให้ตัวอย่างเพิ่มเติม แต่ก็ไม่ได้ให้คำตอบที่ชัดเจนซึ่งเป็นแนวปฏิบัติที่ดีที่สุด

มีโซลูชันสากลที่เหมาะสมและเป็นที่ยอมรับโดยทุกคนเพื่อให้แอปพลิเคชันของฉันทำงานทั้ง Ruby <1.9.2 และ> = 1.9.2 หรือไม่

UPDATE

การชี้แจง: ฉันไม่ต้องการเพียงแค่คำตอบเช่น "คุณทำได้ X" - อันที่จริงแล้วฉันได้พูดถึงตัวเลือกที่เป็นปัญหาอยู่แล้ว ฉันต้องการเหตุผลคือทำไมมันเป็นวิธีปฏิบัติที่ดีที่สุดข้อดีและข้อเสียของมันคืออะไรและทำไมจึงควรเลือกในหมู่คนอื่น ๆ


3
สวัสดีฉันใหม่ ใครช่วยอธิบายได้ตั้งแต่แรกเริ่มอะไรคือความแตกต่างระหว่างrequireและrequire_relative?
พันเอก Panic

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เช่นเดียวกับใน ruby ​​1.9.2 ใน ruby ​​1.8 และ 1.9.1


3
คุณต้องการไฟล์ require_relative.rb อย่างไร คุณต้องต้องการ require_relative.rb และต้องใช้ส่วนที่เหลือที่จำเป็น หรือว่าฉันขาดอะไรไป?
ethicalhack3r

7
require_relativeฟังก์ชั่นรวมอยู่ในโครงการส่วนขยายไปยังห้องสมุดหลักทับทิมพบได้ที่นี่: rubyforge.org/projects/extensionsgem install extensionsคุณควรจะสามารถที่จะติดตั้งพวกเขาด้วย จากนั้นในรหัสของคุณเพิ่มบรรทัดต่อไปนี้ก่อนrequire_relative: ต้องการ 'ส่วนขยาย / ทั้งหมด' (มาจากโพสต์ของ Aurril ที่นี่ )
thegreendroid

@ ethicalhack3r เพียงแค่คัดลอกและวางรหัสนั้นที่ด้านบนสุดของสคริปต์ ruby ​​ของคุณหรือถ้าอยู่ในรางให้โยนมันลงใน environment.rb ชั้นนำหรือบางอย่าง
Travis Reeder

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 ('ญาติ / พา ธ ', File.dirname ( FILE ))
Yannick Wurm

1
ฉันไม่คิดว่ามันจะสะอาดกว่านี้อีกแล้ว พวกเขาทั้งคู่มีความสุขเหมือนนรกและเมื่อเลือกระหว่างตัวเลือกที่ไม่ดีสองตัวฉันชอบตัวเลือกที่ต้องการการพิมพ์น้อยกว่า
Theo

6
ดูเหมือนว่า File.expand_path ('../ relpath.x', File.dirname ( FILE )) เป็นสำนวนที่ดีกว่าถึงแม้ว่ามันจะละเอียดมากขึ้นก็ตาม การใช้งานฟังก์ชั่นที่ใช้งานไม่ได้ของไฟล์พา ธ ที่ถูกตีความว่าเป็นพา ธ ไดเร็กทอรีที่มีไดเร็กทอรีที่ไม่มีอยู่พิเศษอาจแตกเมื่อ / ถ้าฟังก์ชันนั้นได้รับการแก้ไข
jpgeek

1
แตกสลายบางที แต่มันก็เป็นเช่นนั้นตลอดไปใน UNIX ไม่มีความแตกต่างระหว่างไดเรกทอรีและไฟล์เมื่อมันมาถึงพา ธ และความละเอียดของ '.. ' - ดังนั้นฉันจึงไม่สูญเสียการนอนหลับ
ธีโอ

6

The 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

@ Theo @GreyCat ใช่ฉันจะตรวจสอบว่ามันจำเป็น ฉันแค่วางตัวอย่างไว้ที่นี่เพื่อให้ผู้คนแสดง โดยส่วนตัวแล้วฉันจะใช้คำตอบของ Greg ต่อไปฉันแค่โพสต์ข้อความนี้เพราะมีคนพูดถึงมันโดยไม่ต้องมีมัน
Paul Hoffer

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')

ใช้ backport gem - มันหนักมากมันต้องใช้โครงสร้างพื้นฐาน rubygems และมีวิธีแก้ไขอื่น ๆ มากมายในขณะที่ฉันแค่ต้องการทำงานกับไฟล์ที่เกี่ยวข้อง

คุณได้ตอบไปแล้วว่าทำไมตัวเลือกเหล่านี้ถึงไม่ใช่ตัวเลือกที่ดีที่สุด

ตรวจสอบว่า 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 เหล่านี้และอัญมณีก็ไม่หนักหนาเท่ากับ backport


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

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__ เป็นลิงก์สัญลักษณ์จะต้องทำการยกเลิกการลงทะเบียน ชื่อพา ธ # realpath จะช่วยเราในสถานการณ์นี้:

ต้องการ "ชื่อพา ธ "
ต้องการ File.join (File.dirname (Pathname.new (__ FILE __). realpath), "lib / someinclude")

2

หากคุณกำลังสร้างอัญมณีคุณจะไม่ต้องการทำให้เสียเส้นทางโหลด

แต่ในกรณีของแอปพลิเคชันแบบสแตนด์อโลนมันสะดวกมากที่จะเพิ่มไดเรกทอรีปัจจุบันลงในโหลดพา ธ อย่างที่คุณทำใน 2 ตัวอย่างแรก

คะแนนของฉันไปที่ตัวเลือกแรกในรายการ

ฉันชอบที่จะเห็นวรรณกรรมแนวปฏิบัติที่ดีที่สุดของทับทิม


1
Re: "ฉันชอบที่จะเห็นวรรณกรรมแนวปฏิบัติที่ดีที่สุดของ Ruby" คุณสามารถดาวน์โหลดเกรกอรี่บราวน์ทับทิมปฏิบัติที่ดีที่สุด นอกจากนี้คุณยังสามารถตรวจสอบเว็บไซต์ Rails Best Practices
Michael Stalker

1

ฉันจะกำหนดตัวเองrelative_requireหากไม่มี (เช่นต่ำกว่า 1.8) จากนั้นใช้ไวยากรณ์เดียวกันทุกที่


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.