Search code examples
rubymethodsrelative-pathenoent

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?

Solution

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