Ruby require_relative not loading file, not throwing error - ruby

I am having trouble getting constant definitions loaded via an external file. I have narrowed the problem down to the following.
require_relative '../../common/config.rb'
A_CONSTANT = 'something'
puts "A_CONSTANT: #{A_CONSTANT}"
When I run this as written, it prints the message correctly. The same constant is declared in the file common/config.rb. The relative path is correct for the location of this file. Just for completeness, the above code is in /watir/dashboard/spec/ex.rb. The constant is declared in /watir/common/config.rb.
As I see it, the above code should error out for a duplicate constant declaration. It does not. If I comment out the constant declaration above and rerun, the puts statement shows an error for 'uninitialized constant.' Any ideas what's wrong?
Edit - The contents of the file common/config.rb are below.
A_CONSTANT = 'something'
On a lark, I changed the filename to common/conf.rb. When I modify the require_relative statement to load the renamed file, I get the results I originally expected. The file is loaded and the second constant declaration throws a warning saying 'already initialized constant.' If I comment out the second declaration, the script runs perfectly.
It appears that the filename 'config.rb' is somehow special when loaded by a relative path. I have use that filename in other scripts where it was in the same folder as the loading script or a sub-folder. This is the first time I have had to move up the tree to load it.

Ruby allows redefining constants, and will only print a warning. Some setting in your Ruby is just hiding that warning from you.

Related

Dir.chdir(File.dirname(__FILE__)) throws Errno::ENOENT

I got a method where I use Dir.chdir(File.dirname(__FILE__)) inside. I am using it so that I can run the ruby file from anywhere and not get this error: No such file or directory # rb_sysopen - 'filename' (Errno::ENOENT).
Using the method for the first time works fine but using it for the second time throws an error. See method and exact error below.
def meth(string)
Dir.chdir(File.dirname(__FILE__))
hash = JSON.parse(File.read("file.json"))
# do something with hash and string
# return some value
end
meth("first string") # this returns what is expected
meth("second string") # this second usage of the method throws the error
Error sample pinpointing the line where I used Dir.chdir(File.dirname(__FILE__)):
dir/decoder.rb:44:in `chdir': No such file or directory # dir_s_chdir - lib (Errno::ENOENT)
Not sure if OS plays a role here, I am using an m1 BigSur on version 11.2.3.
Why is this happening?
What needs to be done so that the method` can be used as much as needed without running into the error?
Your problem here seems to be that __FILE__ is a relative path like dir/decoder.rb and that path becomes invalid after the first time Dir.chdir is used, because that command changes the working directory of your entire Ruby process. I think the solution would be to do something like this in your decoder.rb file:
DecoderDir = File.realpath(File.dirname(__FILE__))
def meth
Dir.chdir(DecoderDir)
# ...
end
I'm guessing that the first time the Ruby interpreter processes the file, that is early enough that the relative path in __FILE__ still refers to the right place. So, at that time, we generate an absolute path for future use.
By the way, a well-behaved library should not run Dir.chdir because it will affect all use of relative paths throughout your entire Ruby process. I pretty much only run Dir.chdir once and I run it near the beginning of my top-level script. If you're making a reusable library, you might want to do something like this to calculate the absolute path of the file you want to open:
DecoderJson = File.join(DecoderDir, 'file.json')

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.

Loading Ruby scripts in SketchUp: LoadError: (eval):0:in `load': no such file to load

I have been trying to manually load Ruby scripts into SketchUp manually, using load. I always get an error back saying the file is non existent even though it is there in the directory.
Here is a sample of my code:
load "H:Document\sclf_color_by_z_1.6.1_1.rbz"
and the error messages:
Error: LoadError: (eval):0:in `load': no such file to load -- H:Document clf_color_by_z_1.6.1_1.rbz>
(eval)
(eval):0
Three issues here:
H:Document\sclf_color_by_z_1.6.1_1.rbz is not a valid path. After the Drive specifier H: you you should have a separator: \ - like so: H:\Document\sclf_color_by_z_1.6.1_1.rbz
Beware escape characters in strings when you program. \ is such a character.
To correct your string you'd have to have something like this:
"H:\\Document\\sclf_color_by_z_1.6.1_1.rbz"
https://en.wikibooks.org/wiki/Ruby_Programming/Strings#Escape_sequences
However, note that the convention for Ruby is to use forward slashes - even on Windows: "H:/Document/clf_color_by_z_1.6.1_1.rbz"
You are trying to load an RBZ file here. This is not the same as an RB file. An RBZ is a packaged SketchUp extension (actually a ZIP file). To programmatically install an RBZ you must use Sketchup.install_from_archive("H:/Document/clf_color_by_z_1.6.1_1.rbz")
http://www.sketchup.com/intl/en/developer/docs/ourdoc/sketchup#install_from_archive
Note that Sketchup.install_from_archive is nothing like load - it permanently installs the extension to SketchUp where as load would be just for that session.
Whenever you have a filepath that you think should be on disk - as the system whether it can find it: File.exist?("H:\Document\sclf_color_by_z_1.6.1_1.rbz") If that return false you know you need to carefully check your path again checking for syntax errors and typos.
You should use File.join() method. In your case:
You can't use load for a .rbz file but you can use Sketchup.install_from_archive() as thomthom said
So in your case your can simply do:
file = File.join( 'H:', 'Document' , 'sclf_color_by_z_1.6.1_1.rbz' )
Sketchup.install_from_archive file

Is there a way to force a required file to be reloaded in Ruby?

Yes, I know I can just use load instead of require. But that is not a good solution for my use case:
When the app boots, it requires a config file. Each environment has its own config. The config sets constants.
When the app boots, only one environment is required. However, during testing, it loads config files multiple times to make sure there are no syntax errors.
In the testing environment, the same config file may be loaded more than once. But I don't want to change the require to load because every time the a spec runs, it reloads the config. This should be done via require, because if the config has already been loaded, it raises already initialized constant warnings.
The cleanest solution I can see is to manually reset the require flag for the config file after any config spec.
Is there a way to do that in Ruby?
Edit: adding code.
When the app boots it calls the init file:
init.rb:
require "./config/environments/#{ ENV[ 'RACK_ENV' ]}.rb"
config/environments/test.rb:
APP_SETTING = :foo
config/environments/production.rb:
APP_SETTING = :bar
spec/models/config.rb: # It's not a model spec...
describe 'Config' do
specify do
load './config/environments/test.rb'
end
specify do
load './config/environments/production.rb'
end
Yes it can be done. You must know the path to the files that you want to reload. There is a special variable $LOADED_FEATURES which stores what has been loaded, and is used by require to decide whether to load a file when it is requested again.
Here I am assuming that the files you want to re-require all have the unique path /myapp/config/ in their name. But hopefully you can see that this would work for any rule about the path name you can code.
$LOADED_FEATURES.reject! { |path| path =~ /\/myapp\/config\// }
And that's it . . .
Some caveats:
require does not store or follow any kind of dependency tree, to know what it "should" have loaded. So you need to ensure the full chain of requires starting with the require command you run in the spec to re-load the config, and including everything you need to be loaded, is covered by the removed paths.
This will not unload class definitions or constants, but simply re-load the files. In fact that is literally what require does, it just calls load internally. So all the warning messages about re-defining constants will also need to be handled by un-defining the constants you expect to see defined in the files.
There is probably a design of your config and specs that avoids the need to do this.
if you really want to do this, here's one approach that doesn't leak into your test process. Fork a process for every config file you want to test, communicate the status back to the test process via IO.pipe and fail/succeed the test based on the result.
You can go as crazy as you want with the stuff you send down the pipe...
Here's some quick and dirty example to show you what I mean.
a config
# foo.rb
FOO = "from foo"
another config
# bar.rb
FOO = "from bar"
some faulty config
# witherror.rb
asdf
and your "test"
# yourtest.rb
def load_config(writer, config_file)
fork do
begin
require_relative config_file
writer.write "success: #{FOO}\n"
rescue
writer.write "fail: #{$!.message}\n"
end
writer.close
exit # maybe this is even enough to NOT make it run your other tests...
end
end
rd, writer = IO.pipe
load_config(writer, "foo.rb")
load_config(writer, "bar.rb")
load_config(writer, "witherror.rb")
writer.close
puts rd.read
puts rd.read
puts rd.read
puts FOO
The output is:
success: from foo
success: from bar
fail: undefined local variable or method `asdf' for main:Object
yourtest.rb:24:in `<main>': uninitialized constant FOO (NameError)
as you can see, the FOO constant doesn't leak into your test process etc.
Of course you're only through half way because there's more to it like, making sure only one process runs the test etc.
Frankly, I don't think this is a good idea, no matter what approach you chose because you'll open a can of worms and imho there's no really clean way to do this.

programatically invoking Sass::Exec::SassConvert generates wrong syntax

Sass::Exec::SassConvert.new(["{path_to_scss_file}.scss", "{path_to_css_file_to_generate}.css"]).process_result
When I run following command from inside a ruby script, the generated file contains "sass"-syntax instead of valid css
When I try to add a set_opts command to specify "-F scss -T css", just to be sure the convertor knows what to do, it throws an error:
NoMethodError: undefined method `banner=' for ["-F scss -T css"]:Array
The goal is to compile scss files to css files from inside an ant build script.
Everything is working except for the wrong syntax issue.
Is there anything I'm missing here?
I rewrote the script to use Sass::Engine instead of SassConvert.
After looking through the code of the SassConvert class, the following fragment pointed me to the solution:
unless [:scss, :sass].include?(#options[:to])
raise "Unknown format for sass-convert --to: #{name}"
end
It appears the SassConvert class only converts CSS files to .scss or .sass, not the other way around.
Also, the correct way to add option flags when calling Sass::Exec::SassConvert, is by doing the following:
sassConvert = Sass::Exec::SassConvert.new(["-F scss", "-T sass", "{path_to_from_file}", "{path_to_file_to_generate}"]
sassConvert.parse!
sassConvert.process_result

Resources