Ruby: require vs require_relative - best practice to workaround running in both Ruby <1.9.2 and >=1.9.2

匿名 (未验证) 提交于 2019-12-03 01:54:01

问题:

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
  • require './path/to/file'
  • check if RUBY_VERSION 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 '
    '
  • 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?

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 

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

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

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


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!