Ruby: request vs notify_relative - cách tốt nhất để khắc phục sự cố khi chạy trong cả Ruby <1.9.2 và> = 1.9.2


153

Cách thực hành tốt nhất là gì nếu tôi muốn requiremột tệp tương đối trong Ruby tôi muốn nó hoạt động trong cả 1.8.x và> = 1.9.2?

Tôi thấy một vài lựa chọn:

  • cứ làm đi $LOAD_PATH << '.' và quên mọi thứ
  • làm $LOAD_PATH << File.dirname(__FILE__)
  • require './path/to/file'
  • kiểm tra xem RUBY_VERSION<1.9.2, sau đó xác định require_relativerequire, sử dụng require_relativeở mọi nơi cần thiết sau đó
  • kiểm tra nếu require_relativeđã tồn tại, nếu có, cố gắng tiến hành như trong trường hợp trước
  • sử dụng các cấu trúc kỳ lạ như - than ôi dường như chúng không hoạt động trong Ruby 1.9, bởi vì, ví dụ:
    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>'
  • Ngay cả xây dựng kỳ lạ: dường như hoạt động, nhưng nó kỳ lạ và không hoàn toàn đẹp.
    require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')
  • Sử dụng đá quý backports - loại này nặng, nó đòi hỏi cơ sở hạ tầng rubygems và bao gồm hàng tấn cách giải quyết khác, trong khi tôi chỉ muốnrequire làm việc với các tệp tương đối.

Có một câu hỏi liên quan chặt chẽ tại StackOverflow đưa ra một số ví dụ khác, nhưng nó không đưa ra câu trả lời rõ ràng - đó là một cách thực hành tốt nhất.

Có bất kỳ giải pháp phổ quát, được mọi người chấp nhận để làm cho ứng dụng của tôi chạy trên cả Ruby <1.9.2 và> = 1.9.2 không?

CẬP NHẬT

Làm rõ: Tôi không muốn chỉ có câu trả lời như "bạn có thể làm X" - thực tế, tôi đã đề cập đến hầu hết các lựa chọn trong câu hỏi. Tôi muốn lý do , tức là tại sao nó là một thực tiễn tốt nhất, ưu và nhược điểm của nó là gì và tại sao nó nên được chọn trong số những thứ khác.


3
Xin chào tôi là người mới. Ai đó có thể giải thích từ sự khởi đầu của Haiti, sự khác biệt giữa requirerequire_relative?
Đại tá Panic

3
Trong Ruby 1.8 cũ hơn nếu bạn chạy tệp a.rb và muốn làm cho trình thông dịch đọc và phân tích nội dung của tệp b.rbtrong thư mục hiện tại (thường là cùng một thư mục với a.rb), bạn chỉ cần viết require 'b'và nó sẽ ổn vì đường dẫn tìm kiếm mặc định bao gồm thư mục hiện tại. Trong Ruby 1.9 hiện đại hơn, bạn sẽ phải viết require_relative 'b'trong trường hợp này, vì require 'b'sẽ chỉ tìm kiếm trong các đường dẫn thư viện tiêu chuẩn. Đó là điều phá vỡ khả năng tương thích tiến và lùi đối với các tập lệnh đơn giản hơn sẽ không được cài đặt đúng cách (ví dụ: tự cài đặt tập lệnh).
GreyCat

Bây giờ bạn có thể sử dụng backportschỉ để require_relativexem câu trả lời của tôi ...
Marc-André Lafortune

Câu trả lời:


64

Một cách giải quyết cho điều này vừa được thêm vào đá quý 'aws' nên tôi nghĩ tôi sẽ chia sẻ vì nó được lấy cảm hứng từ bài đăng này.

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

Điều này cho phép bạn sử dụng require_relativenhư bạn làm trong ruby ​​1.9.2 trong ruby ​​1.8 và 1.9.1.


3
Làm thế nào để bạn yêu cầu tệp request_relative.rb? Bạn phải yêu cầu request_relative.rb và sau đó request_relative phần còn lại của yêu cầu. Hay tôi đang thiếu một cái gì đó?
ethicalhack3r

7
Các require_relativechức năng được bao gồm trong một dự án mở rộng của thư viện lõi Ruby, tìm thấy ở đây: rubyforge.org/projects/extensions Bạn sẽ có thể cài đặt chúng với gem install extensions. Sau đó, trong mã của bạn thêm dòng sau vào trước require_relative: yêu cầu 'phần mở rộng / tất cả' (có nguồn gốc từ bài đăng của Aurril tại đây )
thegreendroid

@ ethicalhack3r chỉ cần sao chép và dán mã đó vào đầu tập lệnh ruby ​​của bạn hoặc nếu trong đường ray, hãy ném nó vào môi trường hàng đầu.rb hoặc một cái gì đó.
Travis Reeder

46

Trước khi tôi thực hiện bước nhảy tới 1.9.2, tôi đã sử dụng những điều sau đây cho người thân yêu cầu:

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

Lần đầu tiên bạn thấy nó hơi kỳ lạ, vì có vẻ như có thêm '..' khi bắt đầu. Lý do là expand_pathsẽ mở rộng một đường dẫn liên quan đến đối số thứ hai và đối số thứ hai sẽ được hiểu như thể đó là một thư mục. __FILE__rõ ràng không phải là một thư mục, nhưng điều đó không quan trọng vì expand_pathkhông quan tâm đến việc các tệp có tồn tại hay không, nó sẽ chỉ áp dụng một số quy tắc để mở rộng những thứ như .., .~. Nếu bạn có thể vượt qua "waamineute ban đầu thì không có thêm ..ở đó à?" Tôi nghĩ rằng dòng trên hoạt động khá tốt.

Giả sử __FILE__/absolute/path/to/file.rb, những gì xảy ra là expand_pathsẽ xây dựng chuỗi /absolute/path/to/file.rb/../relative/path, và sau đó áp dụng một quy tắc mà nói rằng ..nên loại bỏ các thành phần đường dẫn trước khi nó ( file.rbtrong trường hợp này), trở về/absolute/path/to/relative/path .

Đây có phải là thực hành tốt nhất? Phụ thuộc vào ý của bạn là gì, nhưng có vẻ như nó nằm trên cơ sở mã Rails, vì vậy tôi muốn nói rằng ít nhất đó là một thành ngữ đủ phổ biến.


1
Tôi thấy điều này thường là tốt. Nó xấu, nhưng nó có vẻ hoạt động tốt.
yfeldblum

12
dọn dẹp một chút: yêu cầu File.Exand_path ('tương đối / đường dẫn', File.dirname ( FILE ))
Yannick Wurm

1
Tôi không nghĩ nó sạch hơn nhiều, nó chỉ dài hơn. Cả hai đều chạy trốn như địa ngục, và khi lựa chọn giữa hai tùy chọn xấu, tôi thích cái nào cần ít gõ hơn.
Theo

6
Có vẻ như File.Exand_path ('../ relpath.x', File.dirname ( FILE )) là một thành ngữ tốt hơn, mặc dù nó dài dòng hơn. Dựa vào chức năng bị phá vỡ có thể tranh cãi của đường dẫn tệp đang được hiểu là đường dẫn thư mục có thêm thư mục không tồn tại có thể bị hỏng khi / nếu chức năng đó được sửa.
jpgeek

1
Có thể bị hỏng, nhưng đó là cách đó mãi mãi trong UNIX. Không có sự khác biệt giữa một thư mục và một tệp khi nói đến đường dẫn và độ phân giải của '..' - vì vậy tôi không mất bất kỳ giấc ngủ nào trên đó.
Theo

6

Pickaxe có một đoạn cho cái này cho 1.8. Đây là:

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

Về cơ bản nó chỉ sử dụng những gì Theo trả lời, nhưng vì vậy bạn vẫn có thể sử dụng require_relative.


Làm cách nào để kiểm tra xem đoạn mã này có nên được kích hoạt hay không? Sử dụng $RUBY_VERSIONhoặc bằng cách kiểm tra nếu require_relativetồn tại trực tiếp?
GreyCat

1
Luôn luôn loại vịt, kiểm tra nếu require_relativeđược xác định.
Theo

@Theo @GreyCat có, tôi sẽ kiểm tra xem có cần không. Tôi chỉ đặt đoạn trích ở đây để mọi người thể hiện. Cá nhân, tôi sử dụng câu trả lời của Greg dù sao đi nữa, tôi thực sự chỉ đăng bài này vì ai đó đã đề cập đến nó mà không có nó.
Paul Hoffer

6
$LOAD_PATH << '.'

$LOAD_PATH << File.dirname(__FILE__)

Đó không phải là một thói quen bảo mật tốt: tại sao bạn nên để lộ toàn bộ thư mục của mình?

require './path/to/file'

Điều này không hoạt động nếu RUBY_VERSION <1.9.2

sử dụng các công trình kỳ lạ như

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

Thậm chí xây dựng weirder:

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

Sử dụng đá quý backports - loại này nặng, nó đòi hỏi cơ sở hạ tầng rubygems và bao gồm hàng tấn cách giải quyết khác, trong khi tôi chỉ muốn làm việc với các tệp tương đối.

Bạn đã trả lời tại sao đây không phải là những lựa chọn tốt nhất.

kiểm tra xem RUBY_VERSION <1.9.2, sau đó xác định request_relative là yêu cầu, sử dụng allow_relative ở mọi nơi mà sau đó cần thiết

kiểm tra xem request_relative đã tồn tại chưa, nếu có, hãy thử tiếp tục như trong trường hợp trước

Điều này có thể hoạt động, nhưng có cách an toàn và nhanh chóng hơn: để đối phó với ngoại lệ 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

Tôi là một fan hâm mộ của việc sử dụng đá quý rbx-request-Rel ( nguồn ). Ban đầu nó được viết cho Rubinius, nhưng nó cũng hỗ trợ MRI 1.8.7 và không làm gì trong 1.9.2. Yêu cầu đá quý rất đơn giản và tôi không phải ném đoạn mã vào dự án của mình.

Thêm nó vào Gemfile của bạn:

gem "rbx-require-relative"

Rồi require 'require_relative'trước em require_relative.

Ví dụ: một trong các tệp thử nghiệm của tôi trông như thế này:

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

Đây là giải pháp sạch nhất trong số các IMO này và đá quý không nặng bằng backport.


4

Các backportsviên ngọc bây giờ cho phép tải cá nhân của backports.

Sau đó, bạn có thể chỉ cần:

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

Điều này requiresẽ không ảnh hưởng đến các phiên bản mới hơn và cũng sẽ không cập nhật bất kỳ phương thức dựng sẵn nào khác.


3

Một lựa chọn khác là nói cho người phiên dịch tìm đường nào để tìm kiếm

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

3

Một vấn đề tôi chưa từng thấy được chỉ ra với các giải pháp dựa trên __FILE__ là chúng có liên quan đến các liên kết tượng trưng. Ví dụ nói tôi có:

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

Kịch bản chính, điểm vào, ứng dụng là foo.rb. Tập tin này được liên kết đến ~ / Sc scripts / foo trong $ PATH của tôi. Câu lệnh yêu cầu này bị hỏng khi tôi thực thi 'foo':

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

Vì __FILE__ là ~ / Sc scripts / foo nên câu lệnh yêu cầu ở trên tìm kiếm ~ / ScScript / foo / lib / someinclude.rb mà rõ ràng là không tồn tại. Giải pháp rất đơn giản. Nếu __FILE__ là một liên kết tượng trưng, ​​nó cần được hủy đăng ký. Pathname # realpath sẽ giúp chúng ta trong tình huống này:

yêu cầu "tên đường dẫn"
yêu cầu File.join (File.dirname (Pathname.new (__ FILE __). realpath), "lib / someinclude")

2

Nếu bạn đang xây dựng một viên đá quý, bạn sẽ không muốn làm ô nhiễm đường dẫn tải.

Nhưng, trong trường hợp một ứng dụng độc lập, sẽ rất thuận tiện khi chỉ cần thêm thư mục hiện tại vào đường dẫn tải như bạn làm trong 2 ví dụ đầu tiên.

Phiếu bầu của tôi đi đến tùy chọn đầu tiên trong danh sách.

Tôi rất thích nhìn thấy một số tài liệu thực hành tốt nhất của Ruby.


1
Re: "Tôi rất thích xem một số tài liệu thực hành tốt nhất về Ruby." Bạn có thể tải xuống Thực tiễn tốt nhất về Ruby của Ruby Brown . Bạn cũng có thể kiểm tra trang web Rails Best Practice .
Michael Stalker

1

Tôi sẽ tự xác định relative_requirenếu nó không tồn tại (tức là dưới 1.8) và sau đó sử dụng cùng một cú pháp ở mọi nơi.


0

Cách của Ruby on Rails:

config_path = File.expand_path("../config.yml", __FILE__)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.