Ruby autoloading using absolute paths - ruby

For some reason on a project I'm working someone has create a gem which does autoloading like this:
[
[:Utils, 'utils.rb'],
[:VERSION, 'version.rb'],
[:SomeOtherClass, 'some_other_class.rb'],
].each do |sym, fn|
autoload sym, File.join(MyGem.gem_root, 'lib/my_gem', fn)
end
where MyGem.gem_root gives the absolute path to the gem location, e.g. /path/to/my_gem. I am curious why this might be better (or worse) than doing something like where we rely on the gem loadpath being setup correctly:
[
[:Utils, 'utils'],
[:VERSION, 'version'],
[:SomeOtherClass, 'some_other_class'],
].each do |sym, fn|
autoload sym, File.join(my_gem, fn)
end
Personally I find it more pleasant to see this (despite the code duplication).
autoload :Utils, 'my_gem/utils'
autoload :VERSION, 'my_gem/version'
autoload :SomeOtherClass, 'my_gem/some_other_class'
Anyway, which way is better if any at all?

Much like in shell scripting, the last two blocks of code subject you to whatever capricious values are in the PATH environment variable. It may or may not load the correct file as a result…
Picture, for instance, a vendored gem and the PATH such that ruby tries to load files from the system gem prior to trying the vendored gem.

Related

Problems with requiring files inside gem [duplicate]

What is the difference between require_relative and require in Ruby?
Just look at the docs:
require_relative complements the builtin method require by allowing you to load a file that is relative to the file containing the require_relative statement.
For example, if you have unit test classes in the "test" directory, and data for them under the test "test/data" directory, then you might use a line like this in a test case:
require_relative "data/customer_data_1"
require_relative is a convenient subset of require
require_relative('path')
equals:
require(File.expand_path('path', File.dirname(__FILE__)))
if __FILE__ is defined, or it raises LoadError otherwise.
This implies that:
require_relative 'a' and require_relative './a' require relative to the current file (__FILE__).
This is what you want to use when requiring inside your library, since you don't want the result to depend on the current directory of the caller.
eval('require_relative("a.rb")') raises LoadError because __FILE__ is not defined inside eval.
This is why you can't use require_relative in RSpec tests, which get evaled.
The following operations are only possible with require:
require './a.rb' requires relative to the current directory
require 'a.rb' uses the search path ($LOAD_PATH) to require. It does not find files relative to current directory or path.
This is not possible with require_relative because the docs say that path search only happens when "the filename does not resolve to an absolute path" (i.e. starts with / or ./ or ../), which is always the case for File.expand_path.
The following operation is possible with both, but you will want to use require as it is shorter and more efficient:
require '/a.rb' and require_relative '/a.rb' both require the absolute path.
Reading the source
When the docs are not clear, I recommend that you take a look at the sources (toggle source in the docs). In some cases, it helps to understand what is going on.
require:
VALUE rb_f_require(VALUE obj, VALUE fname) {
return rb_require_safe(fname, rb_safe_level());
}
require_relative:
VALUE rb_f_require_relative(VALUE obj, VALUE fname) {
VALUE base = rb_current_realfilepath();
if (NIL_P(base)) {
rb_loaderror("cannot infer basepath");
}
base = rb_file_dirname(base);
return rb_require_safe(rb_file_absolute_path(fname, base), rb_safe_level());
}
This allows us to conclude that
require_relative('path')
is the same as:
require(File.expand_path('path', File.dirname(__FILE__)))
because:
rb_file_absolute_path =~ File.expand_path
rb_file_dirname1 =~ File.dirname
rb_current_realfilepath =~ __FILE__
Summary
Use require for installed gems
Use require_relative for local files
require uses your $LOAD_PATH to find the files.
require_relative uses the current location of the file using the statement
require
Require relies on you having installed (e.g. gem install [package]) a package somewhere on your system for that functionality.
When using require you can use the "./" format for a file in the current directory, e.g. require "./my_file" but that is not a common or recommended practice and you should use require_relative instead.
require_relative
This simply means include the file 'relative to the location of the file with the require_relative statement'. I generally recommend that files should be "within" the current directory tree as opposed to "up", e.g. don't use
require_relative '../../../filename'
(up 3 directory levels) within the file system because that tends to create unnecessary and brittle dependencies. However in some cases if you are already 'deep' within a directory tree then "up and down" another directory tree branch may be necessary. More simply perhaps, don't use require_relative for files outside of this repository (assuming you are using git which is largely a de-facto standard at this point, late 2018).
Note that require_relative uses the current directory of the file with the require_relative statement (so not necessarily your current directory that you are using the command from). This keeps the require_relative path "stable" as it always be relative to the file requiring it in the same way.
From Ruby API:
require_relative complements the
builtin method require by allowing you
to load a file that is relative to the
file containing the require_relative
statement.
When you use require to load a file,
you are usually accessing
functionality that has been properly
installed, and made accessible, in
your system. require does not offer a
good solution for loading files within
the project’s code. This may be useful
during a development phase, for
accessing test data, or even for
accessing files that are "locked" away
inside a project, not intended for
outside use.
For example, if you have unit test
classes in the "test" directory, and
data for them under the test
"test/data" directory, then you might
use a line like this in a test case:
require_relative "data/customer_data_1"
Since neither
"test" nor "test/data" are likely to
be in Ruby’s library path (and for
good reason), a normal require won’t
find them. require_relative is a good
solution for this particular problem.
You may include or omit the extension
(.rb or .so) of the file you are
loading.
path must respond to to_str.
You can find the documentation at http://extensions.rubyforge.org/rdoc/classes/Kernel.html
The top answers are correct, but deeply technical. For those newer to Ruby:
require_relative will most likely be used to bring in code from another file that you wrote.
for example, what if you have data in ~/my-project/data.rb and you want to include that in ~/my-project/solution.rb? in solution.rb you would add require_relative 'data'.
it is important to note these files do not need to be in the same directory. require_relative '../../folder1/folder2/data' is also valid.
require will most likely be used to bring in code from a library someone else wrote.
for example, what if you want to use one of the helper functions provided in the active_support library? you'll need to install the gem with gem install activesupport and then in the file require 'active_support'.
require 'active_support/all'
"FooBar".underscore
Said differently--
require_relative requires a file specifically pointed to relative to the file that calls it.
require requires a file included in the $LOAD_PATH.
I just saw the RSpec's code has some comment on require_relative being O(1) constant and require being O(N) linear. So probably the difference is that require_relative is the preferred one than require.
I want to add that when using Windows you can use require './1.rb' if the script is run local or from a mapped network drive but when run from an UNC \\servername\sharename\folder path you need to use require_relative './1.rb'.
I don't mingle in the discussion which to use for other reasons.
absolute path
require './app/example_file.rb'
shortened name
require_relative 'example_file'

How does an executable within a gem reference the greater gem?

Say that my gem is VideoPlayer. The folders tructure is:
VideoPlayer/
/bin
vidplay.rb
/lib
VideoPlayer.rb
Subtitler.rb
Screenshotter.rb
I want people to invoke vidplay from the command line, and for vidplay to reference code in the VideoPlayer, Subtitler and Screenshotter files.
If I just write, within vidplay.rb, require '../lib/VideoPlayer.rb', it will throw an error, saying that it cannot require such file. I thought "Maybe it automatically requires everything in lib/", but it apparently doesn't; if I don't require anything, it'll say that VideoPlayer is an uninitialised constant.
So how does this work?
I usually add the lib dir to the library load path ($:). You can add this to the top of your bin file.
lib = File.expand_path('../../lib', __FILE__)
$:.unshift(lib) unless $:.include?(lib)
Then you can do a normal require:
require 'videoplayer'
Hope this helps.

Why is .gemspec using so complicated code to get the lib directory (Ruby)?

I've tried to create a Ruby gem by doing 'bundle gem [gem_name]' and all went well (there is a main folder and inside there is lib and spec folders). In the .gemspec file I saw this:
lib = File.expand_path('../lib', __FILE__)
while produces an absolute path to the /lib. However, the same result can be accomplished with:
File.expand_path('lib')
There was some explanation in this post File.expand_path("../../Gemfile", __FILE__) How does this work? Where is the file? on how complicated the first approach is, so I was wondering, does it really have an advantage relative to the second and simpler approach?
File.expand_path assumes Dir.getwd as the default reference point, which is not necessarily the same as "#{__FILE__}/.." (or Ruby 2.0's __dir__).
Since Ruby 2.0, you can write:
lib = File.expand_path('lib', __dir__)
which is better than the original.
as long as you are intending for your destination filepath string to be the current working directory, Ruby defaults that second parameter for you. If for some reason you'd want to create an absolute path to another location, you can pass the second parameter:
source: http://ruby-doc.org/core-1.9.3/File.html#method-c-expand_path

Ruby require a code snippet from github

Is there anyway to get Ruby's require statement to download a file from somewhere like github rather than just the local file system?
Update: Sorry I should have made the question clearer. I want to download a file that contains Ruby module and import it into my script rather than just downloading an image or some other arbitrary file within my script.
In other words something like this
require 'http:\\github.com\myrepo\snippet.rb'
puts 'hi'
By default, this is not possible. Also, it's not a good idea for security reasons.
You have a couple of alternatives. If the file you want to include is a Gem and Git repository, then you can use Bundler to download and package the dependency in your project. Then you'll be able to require the file directly in your source code.
This is the best and safest way to include an external dependency.
If you trust the source and you really know what you are doing, you can download the file using Net::HTTP (or any other HTTP library) and eval the body directly in your Ruby code.
You can package everything in a custom require_remote function.
You could download and eval it
require "open-uri"
alias :require_old :require
def require(path)
return false if $".include?(path)
unless path=~ /\Ahttp:\/\/
return require_old(path)
end
eval(open(path).read)
$"<< path
true
end
Be aware, this code has no error checking for network outages nonexisting files, ... . I also believe it is in general not a good idea to require libraries this way, there are security and reliability problems in this approach. But maybe you have a valid usecase for this.
you can include a remote gem from within Gemfiles then it will download when you run bundle install
After reading this question and answers I wanted something a little more bullet proof and verbose that used a paradigm of creating a local file from a repo and then requiring it, only if it didn't already exist locally already. The request for the repo version is explicit via the method repo_require. Used on files you control, this approach improves security IMO.
# try local load
def local_require(filename, relative_path)
relative_flname = File.join(relative_path, filename)
require_relative(relative_flname)
end
# try loading locally first, try repo version on load error
# caution: only use with files you control access to!
def repo_require(raw_repo_prefix, filename, relative_path = '')
local_require(filename, relative_path)
rescue LoadError => e
puts e.message
require 'open-uri'
tempdir = Dir.mktmpdir("repo_require-")
temp_flname = File.join(tempdir, File.basename(filename))
return false if $LOADED_FEATURES.include?(temp_flname)
remote_flname = File.join(raw_repo_prefix, filename)
puts "file not found locally, checking repo: #{remote_flname}"
begin
File.open(temp_flname, 'w') do |f|
f.write(URI.parse(remote_flname).read)
end
rescue OpenURI::HTTPError => e
raise "Error: Can't load #{filename} from repo: #{e.message} - #{remote_flname}"
end
require(temp_flname)
FileUtils.remove_entry(tempdir)
end
Then you could call repo_require like this:
repo_require('https://raw.githubusercontent.com/username/reponame/branch',
'filename', 'relative_path')
The relative_path would the the relative path you would use for the file if the repo was locally installed. For example, you may have something like require_relative '../lib/utils.rb'. In this example filename='lib/utils.rb' and relative_path='..'. This information allows the repo url to be constructed correctly as it does not use the relative path portion.

Ruby gem testing before deployment

I'm creating a gem which has
several scripts in the bin directory
the utility classes in the lib directory
and several tests in the test directory
supertool
bin
toolA
toolB
lib
supertool
supertool.rb
helper.rb
test
tc_main.rb
tc_etc.rb
Now, to run the tests before I even install the gem, I have the following snippet at the top of my tests:
base = File.basename(Dir.pwd)
if base == 'test' || base =~ /supertool/
Dir.chdir('..') if base == 'test'
$LOAD_PATH.unshift(Dir.pwd + '/lib')
Dir.chdir('test') if base =~ /supertool/
end
This seems tedious though, especially if I have to put these in the scripts in the bin directory too. Is there a better way of setting up the environment so we can test gems before they are installed? I'm sure it's something simple that I just can't find. A simple link to the right place would help a lot :)
I'm not sure what you're trying to achieve with that script. It doesn't seem to have anything to do with gems...
Is it so that you can run ruby tc_main.rb from within the test directory (or ruby test/tc_main.rb from the base dir), and have it set the load path appropriately? If so, here's a much nicer way:
In your test directory, create a test_helper.rb file. In that file, put this
$LOAD_PATH << File.expand_path( File.dirname(__FILE__) + '/../lib' )
And in all your test files, set the first line to
require 'test_helper'
If you have subdirectories inside your test dir, then files in those subdirs can just do
require '../test_helper'
Take a look at hoe gem, it is a helper for other gems.

Resources