akonsu
akonsu

Reputation: 29536

__FILE__ variable in imported files

I have two files in two different directories:

module MyModule
  def my_method path
    p File.join (File.dirname __FILE__), path
  end
end

and

require_relative '../modules/mymodule' # definition of MyModule
class MyClass
  extend MyModule
  my_method 'my_file.yml'
end

I am getting output like my_home_dir/modules/my_file.yml but I want it to be my_home_dir/files/my_file.yml where files is the name of the directory where MyClass is defined.

I know I can use full path when I call my_method but is there a way for imported files to still have __FILE__ set to the name of the importing file?

Basically in my_method I need to have the full path of the file and I want to pass just a path relative to my calling file's path.

Upvotes: 2

Views: 468

Answers (3)

zires
zires

Reputation: 574

Ruby 2.7 adds Module#const_source_location

const_source_location(:MyMoudule)

Upvotes: 0

Steve Byrne
Steve Byrne

Reputation: 123

There's a simpler method available if you can pass in a block; use the block's binding:

# In example.rb:
module Example
  def execute_if_main(&block)
    if $PROGRAM_NAME == eval("__FILE__", block.binding)
      yield
    end
  end
end

Then in the test file:

# in test.rb:
require_relative 'example.rb'

include Example

execute_if_main do
  puts "hi, I'm being loaded"
end

This will execute the block only if test.rb is being loaded directly by the Ruby interpreter. If test.rb is loaded instead via some other file via require, the block at the end won't be executed (which is the idea)

Upvotes: 2

the Tin Man
the Tin Man

Reputation: 160551

__FILE__ always is the name of the file containing the __FILE__ variable, so saying my_method will always return where my_method is defined, not where MyClass calls it.

You can probably get at the information you want using caller:

module MyModule
  def my_method path
    p caller
  end
end

include MyModule # definition of MyModule
class MyClass
  extend MyModule
  my_method 'my_file.yml'
end

my_class = MyClass.new

Which outputs:

["test.rb:10:in `<class:MyClass>'", "test.rb:8:in `<main>'"]

Edit:

the caller array has only file names without paths...

Well, I'd hoped you'd know how to work around that but....

This is in test.rb:

require './test2'
class MyClass
  extend MyModule
  my_method __FILE__, 'my_file.yml'
end

my_class = MyClass.new

This is in test2.rb:

module MyModule
  def my_method path, file
    dir = File.dirname(path)
    p caller.map{ |c| File.join(dir, c) }
  end
end

Running test.rb outputs:

["./test.rb:4:in `<class:MyClass>'", "./test.rb:2:in `<main>'"]

Upvotes: 2

Related Questions