问题
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 definerequire_relative
asrequire
, userequire_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
- alas they don\'t seem to work in Ruby 1.9 throughly, because, for example: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>\'
- Even weirder construction:
seems to work, but it\'s weird and not quite good looking.require File.join(File.expand_path(File.dirname(__FILE__)), \'path/to/file\')
- 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.
回答1:
A workaround for this was just added to the 'aws' gem so thought I'd share as it was inspired by this post.
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
This allows you to use require_relative
as you would in ruby 1.9.2 in ruby 1.8 and 1.9.1.
回答2:
Before I made the jump to 1.9.2 I used the following for relative requires:
require File.expand_path('../relative/path', __FILE__)
It's a bit weird the first time you see it, because it looks like there's an extra '..' at the start. The reason is that expand_path
will expand a path relative to the second argument, and the second argument will be interpreted as if it were a directory. __FILE__
obviously isn't a directory, but that doesn't matter since expand_path
doesn't care if the files exist or not, it will just apply some rules to expand things like ..
, .
and ~
. If you can get over the initial "waitaminute isn't there an extra ..
there?" I think that the line above works quite well.
Assuming that __FILE__
is /absolute/path/to/file.rb
, what happens is that expand_path
will construct the string /absolute/path/to/file.rb/../relative/path
, and then apply a rule that says that ..
should remove the path component before it (file.rb
in this case), returning /absolute/path/to/relative/path
.
Is this best practice? Depends on what you mean by that, but it seems like it's all over the Rails code base, so I'd say it's at least a common enough idiom.
回答3:
The Pickaxe has a snippet for this for 1.8. Here it is:
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
It basically just uses what Theo answered, but so you can still use require_relative
.
回答4:
$LOAD_PATH << '.' $LOAD_PATH << File.dirname(__FILE__)
It's not a good security habit: why should you expose your whole directory?
require './path/to/file'
This doesn't work if RUBY_VERSION < 1.9.2
use weird constructions such as
require File.join(File.dirname(__FILE__), 'path/to/file')
Even weirder construction:
require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')
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.
You have already answered why these are not the best options.
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
This may work, but there's safer and quicker way: to deal with the LoadError exception:
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:
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.
回答6:
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.
回答7:
Another option is to tell the interpreter which paths to search
ruby -I /path/to/my/project caller.rb
回答8:
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")
回答9:
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.
回答10:
I would define my own relative_require
if it doesn't exist (i.e. under 1.8) and then use the same syntax everywhere.
回答11:
Ruby on Rails way:
config_path = File.expand_path("../config.yml", __FILE__)
来源:https://stackoverflow.com/questions/4333286/ruby-require-vs-require-relative-best-practice-to-workaround-running-in-both