Ruby 1.9.3 - Locate file local to ruby program regardless of where program is called from - ruby

I've been working on my first Ruby project, and in the process of trying to organize my files into different directories, I've run into trouble with having .rb files load non-ruby files (e.g. .txt files) local to themselves.
For example, suppose a project has the following structure:
myproject/
bin/
runner.rb
lib/
foo.rb
fooinfo.txt
test/
testfoo.rb
And the file contents are as follows:
runner.rb
require_relative '../lib/foo.rb'
foo.rb
File.open('./fooinfo.txt') do |file|
while line = file.gets
puts line
end
end
If I cd to lib and run foo.rb, it has no trouble finding fooinfo.txt in its own directory and printing its contents.
However, if I cd to bin and run runner.rb, I get
in `initialize': No such file or directory - ./fooinfo.txt (Errno::ENOENT)
I assume this is because File.open searches relative to whatever directory the top level program is run from.
Is there a way to ensure that foo.rb can find fooinfo.rb regardless of where it is run/required from (assuming that foo.rb and fooinfo.rb always maintain the same location relative to eachother)?
I'd like to be able to run foo.rb from bin/runner.rb, and a test file in test/, and have it be able to find fooinfo.txt in both cases.
Ideally, I'd like to have a solution that would work even if the entire myproject directory were moved.
Is there something like require_relative that can locate a non-ruby file?

Try using __FILE__ and File.dirname to build absolute paths. For example:
File.open(File.expand_path(File.dirname(__FILE__)) + './fooinfo.txt') do |file|
...
end

In this case, the simplest thing is to just change
File.open('./fooinfo.txt')
to
File.open('../lib/fooinfo.txt')
That will work from from any project subdirectory directly under your project root (including lib/).
The more robust solution, useful in larger projects, is to have a PROJECT_ROOT constant that you can use from anywhere. If you have lib/const.rb:
module Const
PROJECT_ROOT = File.expand_path("..", File.dirname(__FILE__))
end
Then (assuming you've requireed that file) you can use:
File.open(Const::PROJECT_ROOT + '/lib/fooinfo.txt')

Related

Failed to run a very simple ruby script [duplicate]

This question already has answers here:
Ruby: how to "require" a file from the current working dir?
(5 answers)
Closed 6 years ago.
I have create a Ruby class Worker, file name is Worker.rb:
class Worker
def initialize
...
end
def doTask(task_name)
...
end
end
Then, I created another Ruby script file, named run.rb (it requires Worker):
require 'Worker'
worker = Worker.new
worker.doTask("sort")
Both two ruby files are located directly under the project folder:
ProjectFolder/
-- Worker.rb
-- run.rb
I run the run.rb under project folder by command:
ruby run.rb
But get following error:
/Users/John/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- Worker (LoadError)
from /Users/John/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
from runme.rb:1:in `<main>'
Why?
I figured out after checking #Geo 's github project, I should use:
require_relative "worker"
require looks for the required file from the so called load path and not the current directory. Since the syntax for looking in the current directory was awkward Ruby 1.9 introduces require_relative, which looks in the current directory.
Change your code like this:
require_relative "Worker"
instead of
require "Worker"
The reason this does not work is because the current directory is not part of ruby's default load path.
If you run the following command, you will see what the current load path is and it will confirm that the current directory is not part of that path.
ruby -e 'puts $LOAD_PATH'
This should answer your primary question as to why the required file was not loaded.
As for a solution, require_relative will work and is probably the best solution in this case.
There are however still cases were inserting directories into the load path is helpful, if not required. For example say you have a script that can be run anywhere in the file system and you want the flexibility to require a particular version of your co-worker's foo class.
/afs/some_cell/u/john/some_ruby_lib
prod/
foo.rb
bar.rb
prev/
foo.rb
bar.rb
beta/
foo.rb
bar.rb
In a case like this either setting the RUBYSIM var (maybe in a wrapper) or setting the proper include path on the ruby command line can be a useful solution.
Again, your co-worker has not published this as a gem, he is just providing a shared directory.
There are several ways you can insert directories into the load path when it is appropriate, as demonstrated below:
You can use the -I command line flag
ruby -I some_path -e 'puts $LOAD_PATH'
You can set the RUBYLIB env var to include your current directory.
on unix/linix/osx
export RUBYLIB=some_path
on windows
set RUBYLIB=some_path

ruby require command not loading correctly

First post, "Hello World"
I am working through the lynda videos on Ruby and am just getting to the part of requiring content from .rb files in irb. An example patch we made is named contact_info.rb and from irb I am trying to require that file. When executed it comes back with the attached below.
Some light googling made it seem like this is maybe a yosemite issue (running 10.10.3.), but I'm not sure how to troubleshoot.
Thanks all
irb(main):006:0> require contact_info.rb
LoadError: cannot load such file -- contact_info.rb
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in 'require'
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in 'require'
from (irb):6
from /usr/bin/irb:12:in '<main>'
you can use require_relative 'contact_info'.
Assuming the file is in your current directory, type this in your command line:
irb -r './contact_info.rb'
First, note that with any require statement, you omit the file extension:
require 'contact_info'
When you require a file, ruby only looks in certain directories on your computer for the file. You can see which directories those are by running the following code:
p $LOAD_PATH
In ruby 1.8.7, the $LOAD_PATH array included ".", or the current directory, which means your code would have worked. But, including the current directory in $LOAD_PATH was deemed a security risk, so now you have to do something different:
1) One option is to use a relative path for the file you specify in the require statement:
require './contact_info'
The path is relative to the current directory. That works fine if you have this structure:
/some/dir/
your_prog.rb
contact_info.rb
And you switch directories to /some/dir and then run your_prog.rb:
~$ cd /some/dir
/some/dir$ ruby my_prog.rb
The require statement works--no problems. However, what if you do this:
/some/dir$ cd ..
/some$ ruby ./dir/your_prog.rb
Now, the current directory is /some, and require './contact_info' tells ruby to look in the /some directory for contact_info.rb--but it isn't there, so you will get the error:
`require': cannot load such file -- ./contact_info.rb (LoadError)
2) To cure that problem, ruby added require_relative. Paths specified with require_relative are relative to the location of the file that contains the require_relative statement. As a result, the statement:
#your_prog.rb:
require_relative './contact_info'
...will look in the directory containing your_prog.rb for the file contact_info.rb. Now, doing this:
/some$ ruby ./dir/your_prog.rb
will work fine. And in fact, in the require_relative you don't even have to write the ./ in the path:
#your_prog.rb:
require_relative 'contact_info' #Look for contact_info.rb in the same
#directory that contains this file
require_relative '../contact_info' #Look for contact_info.rb one directory
#above the directory that contains this file
I am working through the lynda videos on Ruby and am just getting to
the part of requiring content from .rb files in irb.
In my opinion, it's not a good idea to use irb for much of anything. A better option is to create a couple of files called 1.rb, 2.rb, 3.rb, and do your coding in those files.

Reading files elsewhere in directory w/ Ruby

I've got a project structure as follows:
info.config (just a JSON file w/ prefs+creds)
main.rb
tasks/
test.rb
In both main.rb (at the root of the project), and test.rb (under the tasks folder), I want to be able to read and parse the info.config file. I've figured out how to do that in main.rb with the following:
JSON.parse(File.read('info.config'))
Of course, that doesn't work in test.rb.
Question: How can I read the file from a test.rb even though it's one level deeper in the hierarchy?
Appreciate any guidance I can get! Thanks!
Use relative path:
path = File.join(
File.dirname(File.dirname(File.absolute_path(__FILE__))),
'info.config'
)
JSON.parse(File.read(path))
File.dirname(File.absolute_path(__FILE__)) will give you the directory where test.rb resides. -> (1)
File.dirname(File.dirname(File.absolute_path(__FILE__))) will give you parent directory of (1).
Reference: File::absolute_path, File::dirname
UPDATE
Using File::expand_path is more readable.
path = File.expand_path('../../info.config', __FILE__)
JSON.parse(File.read(path))
What I usually do is:
Create file called environment or similar in your project root. This file has only one purpose - to extend load path:
require 'pathname'
ROOT_PATH = Pathname.new(File.dirname(__FILE__))
$:.unshift ROOT_PATH
Require this file at the beginning of your code. From now on every time you call require, you can use relative_path to you root directory, without worrying where file you are requiring it from is located.
When using File, you can simple do:
File.open(ROOT_PATH.join 'task', 'test.rb')
You can do as below using File::expand_path :
path = File.expand_path("info.config","#{File.dirname(__FILE__)}/..")
JSON.parse(File.read(path))
File.dirname(__FILE__) will give you the path as "root_path_of_your_projet/tasks/".
"#{File.dirname(__FILE__)}/.." will give you the path as "root_path_of_your_projet/". .. means go one level up from the current directory.
File.expand_path("info.config","root_path_of_your_projet/") will give you the actual path to the file as "root_path_of_your_projet/info.config".
You can also use __dir__ instead of File.dirname(__FILE__).
__dir__ : Returns the canonicalized absolute path of the directory of the file from which this method is called.
Hope that explanation helps.

require_relative from present working dir

I built a ruby gem with a binary. I use like this:
myruby "param"
It is a helper for building integration, and needs a setting for each project. I have settings in settings.rb for several projects. Is it possible to require a .rb file based on the present working dir? When I run:
/home/usr/admin/sources/myproject1/ $ myruby start
I want it to require the settings from:
/home/usr/admin/sources/myproject1/settings.rb
How could I do this if it's possible? I tried:
require_relative '#{Dir.pwd}/settings.rb'
which did not work.
File.expand_path('../', __FILE__)
gives you the path to the current directory. Thus if you have a file in bin/foo and you want to require something in lib/foo/settings.rb simply use
require File.join(File.expand_path('../../'), __FILE__), 'lib/foo/settings.rb')
Note the double ../ because the first is required to strip out from __FILE__ the current filename.
If the file is in /home/usr/admin/sources/myproject1/bin/foo
File.expand_path('../', __FILE__)
# => /home/usr/admin/sources/myproject1/bin
File.expand_path('../../', __FILE__)
# => /home/usr/admin/sources/myproject1
File.join(File.expand_path('../../', __FILE__), 'lib/foo/settings.rb')
# => /home/usr/admin/sources/myproject1/lib/foo/settings.rb
If you want to include the file with a relative path from the working directory, use
require File.join(Dir.pwd, 'settings.rb')
However, I don't think it's a good idea to hard-code a path in this way. You may probably want to pass the settings as argument to the command line.
It doesn't really make sense to create a gem that depends on a path of a file hard-coded on your machine.
File.expand_path(File.dirname(__FILE__)) will return the directory relative to the file this command is called from.
The difference between require and require_relative is that require_relative loads files from directory relative to the file where it is written. While require searches for files in directories from $LOAD_PATH if given a relative path or can load files from absolute path, which can be created with File.expand_path

Referencing file location in RSpec Rake task vs. rspec runner

I have this directory structure:
project_dir
spec
person
person_invalid_address_examples.yaml
person_spec.rb
rakefile.rb
The person_spec.rb has this piece of code in it:
describe "Create person tests"
...
context "Person with invalid address" do
invalid_address_examples = []
File.open("person_invalid_address_examples.yaml", "r") do |file|
invalid_address_examples = YAML::load(file)
end
invalid_address_examples.each do |example|
it "Does not allow to create person with #{example[:description]}" do
person.address = example[:value]
result = person.create
result.should_not be_success
end
end
end
...
end
Now when I run from the person directory rspec person_spec.rb everything works as expected. But if I run RSpec rake task from the rakefile I get No such file or directory error... The problem is obviously present also the other way round - if I configure filename with path relative to the rakefile location then RSpec rake task works fine but I get No such file or directory error from the rspec runner.. Is there a way to configure filename with path so that it is working for the RSpec rake task and Rspec runner at the same time?
Whether your File.open works depends on the load path -- ruby looks up that relative path in the dirs in the current load path. You can look at the load path in the special $: variable.
Try looking at the value of this variable compared between both methods of executing the spec, and see how/if it differs.
It may be that the current working directory (basically, what directory you executed the command from, shows up in a list of paths as .) is on the load path, and the current working directory ends up different in your two different methods of running the spec.
Where is your yaml file located? Is your YAML file used only for testing, can you put it wherever you want?
You have various options, but they all depend on supplying either an absolute path, or a relative path that will always be on the load path.
Move the yml file to somewhere that is always on the load path. Your spec dir is probably already on the load path. You can put your yml in ./spec/example.yml. Or put your yml in a subdir, but reference that subdir in the open too -- spec/support/data/examples.yml, and then open "data/examples.yml" (starting from a dir on the load path, data/examples.yml will resolve).
Or, ignoring the load path, you could use the special __FILE__ variable to construct the complete path to your yml file, from it's relative location to the current file. __FILE__ is the file path of the source file where the code referencing it is.
Or, probably better than 2, you could add a directory of example data to the load path in your spec_helper.rb, by constructing a path with __FILE__, and then adding it to the $: variable. For instance, a example_data directory.
Probably #1 is sufficient for your needs though. Put the yml inside your spec directory -- or put it in a subdir of your spec directory, but include that subdir in the open argument.
It's because of
File.open("person_invalid_address_examples.yaml", "r")
It opens the file where the rspec is running.
In your case you should define file more apparently something like this:
file_path = File.expand_path("person_invalid_address_examples.yaml", File.dirname(__FILE__))
File.open(file_path, "r")

Resources