20

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
  • 10,633
  • 12
  • 39
  • 50

5 Answers5

12

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
Darwin
  • 4,686
  • 2
  • 30
  • 22
7

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

akostadinov
  • 17,364
  • 6
  • 77
  • 85
4

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
NZKoz
  • 600
  • 5
  • 11
  • 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 Oct 29 '10 at 01:23
  • Also, dsl and especially eval are not variants. At least for now. I just want to keep things simple. – disfated Oct 29 '10 at 01:32
  • 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. – NZKoz Oct 29 '10 at 01:51
  • 2
    "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. – the Tin Man Oct 29 '10 at 03:50
1

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");
peterk
  • 5,136
  • 6
  • 33
  • 47
1

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.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • thanks for the approach, but as you can see yourself - this is more redundant solution than even 'php style'... – disfated Oct 29 '10 at 12:30
  • 1
    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! – disfated Oct 29 '10 at 13:24
  • You might need to change the line `require 'test'` to `require './test'` – Automatico Apr 30 '13 at 13:05