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

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

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.

Why is the "./" (dot + forward slash) needed in Ruby files when using the require keyword?

When attempting to place Ruby dependency files into a Ruby file, why is the "./" (dot + forward slash) necessary when typing out the file directory? Oddly enough it is only needed when using the require keyword and not the load keyword.
i.e.:
module.rb (the dependency)
module SomeModule
def someMethod
puts "hello"
end
end
method.rb
require "./module.rb"
#require "module.rb" does not work
class Animal
include SomeModule
end
class Person
include SomeModule
end
animal = Animal.new
animal.someMethod
person = Person.new
person.someMethod
#irb --> need to also write require " ./method.rb" to call it
If module.rb and method.rb are in the same directory, instead of using require you should use require_relative. Thus, the top of method.rb would look like this
require_relative 'module'
class Animal
It is only needed when using the require keyword and not the load
keyword. Why?
load, require and require_relative are all methods which take a filename as an argument that is to be loaded. They need to locate the filename passed an an argument
load
load checks for the file in the LOAD PATH which can be accessed using the global variable $LOAD_PATH or $: Even though the current working directory (designated by .) is NOT actually in the LOAD_PATH, the load method acts as if it is so is able to locate a file without explicitly appending the current directory to the file name passed as an argument
require
require is similar to load with two main differences
The first is that it's unaware about the current working directory. It does not add it to the LOAD PATH so once ruby searches it, it doesn't find the file. This is why you have to explicitly tell ruby about the current working directory and how to locate the file from the current directory using ./path_to_file.
If you don't want to add ./, you have to add the current directory to your Load Path
$: << '.'
require 'module.rb'
This will work as the current directory is now in the load path which is where ruby will search for the file.
The second main difference is that when you make multiple calls to require in a file passing it the same filename as an argument, the file will be required only the first time. Ruby keeps track of the files required. However with multiple calls to load with the same filename as an argument, the file is always loaded.
require 'time' => true
require 'time' => false #second call within the same file
require_relative
require_relative searches relative to the file in which the method call was executed which is why you don't need to alter the LOAD PATH by explicitly adding the current working directory

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.

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

Error reading local file in Sinatra

I'm trying to write a Sinatra app that reads in a list from a file, and then spits back a random item from that list.
I'm having trouble figuring out the path to the file to read it, though. Sinatra says 'no such file or directory' when I try to load an item in my browser:
Errno::ENOENT at /wod
No such file or directory - http://localhost:4567/listing.txt
Here is the code:
require 'sinatra'
#list
get /item
puts read_list[rand(#list.size)]
end
def read_list
File.open('listing.txt', 'r').readlines
end
I have the file in /public, which the Sinatra README says is the default location for hosting static files. Furthermore, if I put it in /public I can navigate to localhost:4567/listing.txt and read the file in the browser.
A couple things I noticed:
get /item
isn't correct, it should be:
get '/item' do
If you start your code inside the same directory the Ruby code is in, the current working-directory will be ".", which is where Ruby will look when trying to:
File.open('listing.txt', 'r').readlines
Ruby will actually use './listing.txt' as the path. That's OK if you manually launch the code from the root directory of the application, but that doesn't work well if you try to launch it from anywhere else.
It's better to be explicit about the location of the file when you're actually trying to load something for use with a web server. Instead of relying on chance, there are a couple things you can do to help make it more bullet-proof. Consider this:
def read_list
running_dir = File.dirname(__FILE__)
running_dir = Dir.pwd if (running_dir == '.')
File.open(running_dir + '/public/listing.txt', 'r').readlines
end
File.dirname gets the path information from __FILE__, which is the absolute path and name of the current file running. If the application was started from the same directory as the file, that will be ., which isn't what we want. In that case, we want the absolute path of the current working-directory, which Dir.pwd returns. Then we can append that to the path of the file you want, from the root of the application.
You'll need to do File.read('public/listing.txt', 'r') to get what you want here.
File.open isn't part of Sinatra and doesn't know to look in a specific place for static files, so it just looks in the current working directory.

Resources