Reading files elsewhere in directory w/ Ruby - 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.

Related

Requiring files from a required file

I have a file required.rb required by other files main.rb and tester.rb, each of which is invoked separately and run separately.
Within required.rb, I want to require all files in a subdirectory of the required file. The whole thing looks something like this:
main.rb
lib/
required.rb
req_files/
req1.rb
req2.rb
req3.rb
tester/
tester.rb
The code to import the required files looks like:
Dir[Dir.pwd + "/req_files/*.rb"].each do |file|
require file
end
In suggested strategies I have seen, be it utilizing Dir.pwd or __FILE__, the context applied to required.rb's location is the context of whichever original file required it in the first place, which means that I can't support requiring from both of those files separately with the current setup.
Is there a way to denote a path relative to the actual required.rb?
EDIT :
It's not though, because changing require to require_relative doesn't change the fact that Dir[Dir.pwd + "/req_files/*.rb"] and more specifically Dir.pwd resolves with respect to the original file (main or tester), so it cannot be expressed as is in required and work for both entry points
Also note that required.rb is required via require_relative already from both main.rb and tester.rb.
Is there a way to denote a path relative to the actual required.rb
Yes, kinda. There's another method for this.
http://ruby-doc.org/core-2.4.2/Kernel.html#method-i-require_relative
require_relative(string) → true or false
Ruby tries to load the library named string relative to the requiring file’s path. If the file’s path cannot be determined a LoadError is raised. If a file is loaded true is returned and false otherwise.
I was incorrect regarding __FILE__; Using File.dirname(__FILE__) instead of Dir.pwd works for giving the directory of the actual file versus the directory of the invoking file.

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

Ruby: How to find path relative to where class was instantiated?

I am creating a gem that is a Rack application, so I assume my application is going to be instantiated in a config.ru file. I expect certain paths to be relative to this config.ru file. So how can I get and set the path when the app is initialized?
For example:
Hidden away in my gem:
class MyApp
def initialize
#base_path = get_the_base_path_here
end
def call(env)
html = render_view(#base_path + '/views/index.erb')
end
end
User of the gem's config.ru:
require 'my_app'
run MyApp.new
...and their views directory:
/views
index.erb
Update:
One way to achieve this is to pass in the base path as an argument, but I would like to find a way to achieve this without passing it as an argument.
require 'my_app'
run MyApp.new(File.dirname(__FILE__))
Absolute Path of Current File
In general, you can simply use File.expand_path(__FILE__) to find the absolute path of the current file, which you can then store a variable or global if you like. For example:
$file_path = File.expand_path(__FILE__)
Absolute Path of Current Program
File.expand_path($0) is similar, but returns the program that was called. The distinction is sometimes subtle, but can be useful from time to time.
Creating an Absolute Path to a File in the Same Base Directory
If you want to use the directory name of the location of the current file to address another file, you can use File#join. For example:
File.join File.dirname(File.expand_path(__FILE__)), '.X11-unix'
=> "/tmp/.X11-unix"
Probably not the best way but you can find config.ru with:
$:.find{|path| File.exists? "#{path}/config.ru"}

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

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

Resources