How not to change require_relative after moving files - ruby

I have some question about best way to do this:
I have file framework/helpers/test_helper.rb
it starts with line
require_relative '../../framework/app_manager/app_manager'
After some toughts i want to reorganize framework/helpers folder and move this file to
framework/helpers/test/test_helper.rb
After that i must change first line of this file to
require_relative '../../../framework/app_manager/app_manager'
So my question is - that is the best way to require_relative file, so I will not need to change require_relative line every time i move file from folder to folder.

The best way is not to use require_relative at all.
Put directories that include files you'll need to require in your $LOAD_PATH, and then just require them.
If you change your directory structure later, you just need to change the lines that set up your $LOAD_PATH, and not any require lines.
This is exactly why to use the $LOAD_PATH as ruby intended, and avoid require_relative.
http://joshuapaling.com/blog/2015/03/22/ruby-load-path.html

Related

How to find text file in same directory

I am trying to read a list of baby names from the year 1880 in CSV format. My program, when run in the terminal on OS X returns an error indicating yob1880.txt doesnt exist.
No such file or directory # rb_sysopen - /names/yob1880.txt (Errno::ENOENT)
from names.rb:2:in `<main>'
The location of both the script and the text file is /Users/*****/names.
lines = []
File.expand_path('../yob1880.txt', __FILE__)
IO.foreach('../yob1880.txt') do |line|
lines << line
if lines.size >= 1000
lines = FasterCSV.parse(lines.join) rescue next
store lines
lines = []
end
end
store lines
If you're running the script from the /Users/*****/names directory, and the files also exist there, you should simply remove the "../" from your pathnames to prevent looking in /Users/***** for the files.
Use this approach to referencing your files, instead:
File.expand_path('yob1880.txt', __FILE__)
IO.foreach('yob1880.txt') do |line|
Note that the File.expand_path is doing nothing at the moment, as the return value is not captured or used for any purpose; it simply consumes resources when it executes. Depending on your actual intent, it could realistically be removed.
Going deeper on this topic, it may be better for the script to be explicit about which directory in which it locates files. Consider these approaches:
Change to the directory in which the script exists, prior to opening files
Dir.chdir(File.dirname(File.expand_path(__FILE__)))
IO.foreach('yob1880.txt') do |line|
This explicitly requires that the script and the data be stored relative to one another; in this case, they would be stored in the same directory.
Provide a specific path to the files
# do not use Dir.chdir or File.expand_path
IO.foreach('/Users/****/yob1880.txt') do |line|
This can work if the script is used in a small, contained environment, such as your own machine, but will be brittle if it data is moved to another directory or to another machine. Generally, this approach is not useful, except for short-lived scripts for personal use.
Never put a script using this approach into production use.
Work only with files in the current directory
# do not use Dir.chdir or File.expand_path
IO.foreach('yob1880.txt') do |line|
This will work if you run the script from the directory in which the data exists, but will fail if run from another directory. This approach typically works better when the script detects the contents of the directory, rather than requiring certain files to already exist there.
Many Linux/Unix utilities, such as cat and grep use this approach, if the command-line options do not override such behavior.
Accept a command-line option to find data files
require 'optparse'
base_directory = "."
OptionParser.new do |opts|
opts.banner = "Usage: example.rb [options]"
opts.on('-d', '--dir NAME', 'Directory name') {|v| base_directory = Dir.chdir(File.dirname(File.expand_path(v))) }
end
IO.foreach(File.join(base_directory, 'yob1880.txt')) do |line|
# do lines
end
This will give your script a -d or --dir option in which to specify the directory in which to find files.
Use a configuration file to find data files
This code would allow you to use a YAML configuration file to define where the files are located:
require 'yaml'
config_filename = File.expand_path("~/yob/config.yml")
config = {}
name = nil
config = YAML.load_file(config_filename)
base_directory = config["base"]
IO.foreach(File.join(base_directory, 'yob1880.txt')) do |line|
# do lines
end
This doesn't include any error handling related to finding and loading the config file, but it gets the point across. For additional information on using a YAML config file with error handling, see my answer on Asking user for information, and never having to ask again.
Final thoughts
You have the tools to establish ways to locate your data files. You can even mix-and-match solutions for a more sophisticated solution. For instance, you could default to the current directory (or the script directory) when no config file exists, and allow the command-line option to manually override the directory, when necessary.
Here's a technique I always use when I want to normalize the current working directory for my scripts. This is a good idea because in most cases you code your script and place the supporting files in the same folder, or in a sub-folder of the main script.
This resets the current working directory to the same folder as where the script is situated in. After that it's much easier to figure out the paths to everything:
# Reset working directory to same folder as current script file
Dir.chdir(File.dirname(File.expand_path(__FILE__)))
After that you can open your data file with just:
IO.foreach('yob1880.txt')

Relative paths from inside a file: `../` --> parent directory vs `./` --> file itself?

Suppose file_a and file_b live in the same directory, and file_a contains a require statement that requires file_b. The way to do this seems to be like so:
require File.expand_path('../file_b', __FILE__)
But kind of expected it to look like this instead:
require File.expand_path('./file_a', __FILE__)
I played around with it and sure enough, the ./ version doesn't work and the ../ version does. The ./ version returns a path like path/to/file_a/file_b. Is the concept that code inside file_a lives inside that file, much like file_a "lives" inside it's parent directory? I feel like I just answered my own question, but want to make sure I'm understanding this right.
File.expand_path(file_name [, dir_string] ) -> abs_file_name
Converts a pathname to an absolute pathname. Relative paths are referenced from the current working directory of the process unless dir_string is given, in which case it will be used as the starting point.
File.expand_path treats the second (optional) parameter as dir_string, it doesn't care whether it is actually a directory or not. So it's your job to make sure the second parameter passed in be a path to a directory.
If you want preserve the ./file_a part, you may change the second parameter passed in:
require File.expand_path('./file_a', File.dirname(__FILE__))

Wildcard file requires in Ruby

As an example:
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |f| require f }
This is how RSpec requires all of the ruby files in the support directory and all subdirectories. I know this has to do with "/**/*". What does this mean in Ruby? How does it work?
File.dirname(__FILE__) is the directory where the file is. ** and * are UNIX wildcards. Adding "/support/**/*.rb to the directory points to any file that ends with .rb, which is under an arbitrary depth under the sub-directory support under that directory.
Passing that to Dir[] gives the array of such files. each iterates over such files, and require loads each file.
i believe that the /**/ part means Any directory , and the *.rb means any file that ends with .rb extention, regardless of it's name.
so, basically, you are getting any .rb file that are in any folder in
#{current_dir}/support/#{any_dir}/#{any_file_with_extention.rb}

Requiring file outside of load path in Ruby

I have a gem that I have written that has a number of handlers, each of which has their own ruby file in the gem. I need to add the ability to specify a file on the command line that will be loaded in the same manner as these other handlers. The file will typically not be in the default load path of the gem.
I'm not sure the best way to do this. I could take the filename, and then add the containing directory to the load path and then load the file. I could have the user specify a directory containing handlers to be read instead of specifying the file, or I'm sure there are a better way to do it that I haven't yet thought of.
This was fixed using require_relative and expanding the file path using Dir.pwd:
req_path = File.expand_path(arg, Dir.pwd)
require_relative req_path

File paths in Ruby

So I want to make a file path relative to the directory it is in, in Ruby.
I have a project, and I want it to be able to find the file no matter what directory the project is unzipped into. (Say the code is run on different machines, for example) I can't figure it out for the life of me.
It seems for requires that I can do this:
require File.dirname(__FILE__) + '/comparison'
What can I do for a file that is in a different directory than my src folder?
Instead of listing,
file = 'C:/whole path/long/very_long/file.txt'
I'd like to say:
file = 'file.txt'
or
file = File.helpful_method + 'file.txt'
file = File.join(File.dirname(__FILE__), '..', 'another_dir', 'file.txt')
Replace '..', 'another_dir' with the relative path segments that reach 'file.txt'.
If you're running Ruby 1.9.2 or later, you can use require_relative instead:
require_relative '../somewhere/file.rb'
This doesn't solve the general problem of referring to files by their relative path, but if all you're doing is requiring the file, it should work.

Resources