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

11,080

Solution 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

Solution 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

Solution 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

Solution 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.

Solution 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");
Share:
11,080
disfated
Author by

disfated

Updated on July 08, 2022

Comments

  • disfated
    disfated almost 2 years

    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 )

  • disfated
    disfated over 13 years
    Yaml won't work: config.rb is just an example - there should be some proccessings - not just serialized data. Actually, I need a simple command "exec_file_as_it_was_typed_here(file)". btw, php can do this )
  • disfated
    disfated over 13 years
    Also, dsl and especially eval are not variants. At least for now. I just want to keep things simple.
  • NZKoz
    NZKoz over 13 years
    The short version is that ruby can't do this, the longer version is that you should be defining and executing a DSL rather than relying on a clever hack with scoping.
  • PJP
    PJP over 13 years
    "php can do this" ... trying not to scream... there are untold numbers of websites on the internet that are proof that PHP can do that, and that cause the rest of us to curse the programmers who allowed it.
  • disfated
    disfated over 13 years
    thanks for the approach, but as you can see yourself - this is more redundant solution than even 'php style'...
  • disfated
    disfated over 13 years
    I'm not particularly enthusiastic about metaprogramming, but php is a hell for me. I thought that so powerful dynamic tool like ruby can do such a primitive task. Bad news. It can't. Thanks all for your attention!
  • Automatico
    Automatico about 11 years
    You might need to change the line require 'test' to require './test'