Ruby: require vs require_relative - best practice

2019-01-05 07:05发布

What is the best practice if I want to require a relative file in Ruby and I want it to work in both 1.8.x and >=1.9.2?

I see a few options:

  • just do $LOAD_PATH << '.' and forget everything
  • do $LOAD_PATH << File.dirname(__FILE__)
  • require './path/to/file'
  • check if RUBY_VERSION < 1.9.2, then define require_relative as require, use require_relative everywhere where it's needed afterwards
  • check if require_relative already exists, if it does, try to proceed as in previous case
  • use weird constructions such as
    require File.join(File.dirname(__FILE__), 'path/to/file')
    - alas they don't seem to work in Ruby 1.9 throughly, because, for example:
    $ 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>'
  • Even weirder construction:
    require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')
    seems to work, but it's weird and not quite good looking.
  • Use backports gem - it's kind of heavy, it requires rubygems infrastructure and includes tons of other workarounds, while I just want require to work with relative files.

There's a closely related question at StackOverflow that gives some more examples, but it doesn't give a clear answer - which is a best practice.

Is there are any decent, accepted-by-everyone universal solution to make my application run on both Ruby <1.9.2 and >=1.9.2?

UPDATE

Clarification: I don't want just answers like "you can do X" - in fact, I've already mentioned most of choices in question. I want rationale, i.e. why it is a best practice, what are its pros and cons and why it should be chosen among the others.

11条回答
爷、活的狠高调
2楼-- · 2019-01-05 07:21

The backports gem now allows individual loading of backports.

You could then simply:

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

This require will not affect newer versions, nor will it update any other builtin methods.

查看更多
甜甜的少女心
3楼-- · 2019-01-05 07:22

One issue I've not seen pointed out with the solutions based on __FILE__ is that they break with regards to symlinks. For example say I have:

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

The main script, the entry point, the application is foo.rb. This file is linked to ~/Scripts/foo which is in my $PATH. This require statement is broken when I execute 'foo':

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

Because __FILE__ is ~/Scripts/foo so the require statement above looks for ~/Scripts/foo/lib/someinclude.rb which obviously doesn't exist. The solution is simple. If __FILE__ is a symbolic link it needs to be dereferenced. Pathname#realpath will help us with this situation:

require "pathname"
require File.join(File.dirname(Pathname.new(__FILE__).realpath), "lib/someinclude")
查看更多
甜甜的少女心
4楼-- · 2019-01-05 07:27

I would define my own relative_require if it doesn't exist (i.e. under 1.8) and then use the same syntax everywhere.

查看更多
欢心
5楼-- · 2019-01-05 07:30

I'm a fan of using the rbx-require-relative gem (source). It was originally written for Rubinius, but it also supports MRI 1.8.7 and does nothing in 1.9.2. Requiring a gem is simple, and I don't have to throw code snippets into my project.

Add it to your Gemfile:

gem "rbx-require-relative"

Then require 'require_relative' before you require_relative.

For example, one of my test files looks like this:

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

This is the cleanest solution out of any of these IMO, and the gem isn't as heavy as backports.

查看更多
Emotional °昔
6楼-- · 2019-01-05 07:32

Ruby on Rails way:

config_path = File.expand_path("../config.yml", __FILE__)
查看更多
仙女界的扛把子
7楼-- · 2019-01-05 07:33

If you were building a gem, you would not want to pollute the load path.

But, In the case of a standalone application it is very convenient to just add the current directory to the load path as you do in the first 2 examples.

My vote goes to the first option on the list.

I would love to see some solid Ruby best practices literature.

查看更多
登录 后发表回答