ruby: how to load .rb file in the local context

2020-06-08 03:12发布

问题:

How this simple task can be done in Ruby?
I have some simple config file

=== config.rb
config = { 'var' => 'val' }

I want to load config file from some method, defined in main.rb file so that the local variables from config.rb became local vars of that method.
Something like this:

=== main.rb
Class App
    def loader
        load('config.rb') # or smth like that
        p config['var']   # => "val"
    end
end

I know that i can use global vars in config.rb and then undefine them when done, but i hope there's a ruby way )

回答1:

You certainly could hack out a solution using eval and File.read, but the fact this is hard should give you a signal that this is not a ruby-like way to solve the problem you have. Two alternative designs would be using yaml for your config api, or defining a simple dsl.

The YAML case is the easiest, you'd simply have something like this in main.rb:

Class App
  def loader
      config = YAML.load('config.yml')
      p config['var']   # => "val"
  end
end

and your config file would look like:

--- 
var: val


回答2:

The config file.

{ 'var' => 'val' }

Loading the config file

class App
  def loader
    config = eval(File.open(File.expand_path('~/config.rb')).read)
    p config['var']
  end
end


回答3:

As others said, for configuration it's better to use YAML or JSON. To eval a file

binding.eval(File.open(File.expand_path('~/config.rb')).read, "config.rb") binding.eval(File.read(File.expand_path('~/config.rb')), "config.rb")

This syntax would allow you to see filename in backtraces which is important. See api docs [1].

Updated eval command to avoid FD (file descriptor) leaks. I must have been sleeping or maybe should have been sleeping at that time of the night instead of writing on stackoverflow..

[1] http://www.ruby-doc.org/core-1.9.3/Binding.html



回答4:

I do NOT recommend doing this except in a controlled environment.

Save a module to a file with a predetermined name that defines an initialize and run_it methods. For this example I used test.rb as the filename:

module Test
  @@classvar = 'Hello'
  def initialize
    @who = 'me'
  end

  def get_who
    @who
  end

  def run_it
    print "#{@@classvar} #{get_who()}"
  end
end

Then write a simple app to load and execute it:

require 'test'

class Foo
  include Test
end

END {
  Foo.new.run_it
}

# >> Hello me

Just because you can do something doesn't mean you should. I cannot think of a reason I'd do it in production and only show it here as a curiosity and proof-of-concept. Making this available to unknown people would be a good way to get your machine hacked because the code could do anything the owning account could do.



回答5:

I just had to do a similar thing as I wanted to be able to load a "Ruby DLL" where it returns an anonymous class ( a factory for instances of things ) I created this which keeps track of items already loaded and allows the loaded file to return a value which can be anything - a totally anonymous Class, Module, data etc. It could be a module which you could then "include" in an object after it is loaded and it could could supply a host of "attributes" or methods. you could also add an "unload" item to clear it from the loaded hash and dereference any object it loaded.

module LoadableModule

@@loadedByFile_ = {};

def self.load(fileName)
    fileName = File.expand_path(fileName);
    mod = @@loadedByFile_[fileName];
    return mod if mod;
    begin           
        Thread.current[:loadReturn] = nil;
        Kernel.load(fileName);
        mod = Thread.current[:loadReturn];
        @@loadedByFile_[fileName] = mod if(mod);
    rescue => e
        puts(e);
        puts(e.backtrace);
        mod = nil;
    end
    Thread.current[:loadReturn] = nil;
    mod
end
def self.onLoaded(retVal)
    Thread.current[:loadReturn] = retVal;
end
end 

inside the loaded file:

LoadableModule.onLoaded("a value to return from the loaded file");