Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading external files within a class/module

I have an external file: path_to_external_file.rb with some class definition:

class A
  some_definitions
end

And I want to load that within module B so that the class A defined above can be referred to as B::A. I tried:

class B
  load('path_to_external_file.rb')
end

but A is defined in the main environment, not in B:

A #=> A
B.constants # => []

How can I load external files within some class/module?

Edit Should I read the external files as strings, and evaluate them within Class.new{...}, and include that class within B?

like image 664
sawa Avatar asked Jan 23 '12 03:01

sawa


2 Answers

You cannot. At least using load or require, the Ruby files will always be evaluated in a top context.

You can work around that problem in two ways:

  • Define class B::A directly (but you are probably trying to avoid that)
  • Use eval(File.read("path_to_external_file.rb")) within your B class

Edit: Maybe, this library is interesting for you: https://github.com/dreamcat4/script/blob/master/intro.txt

like image 143
J-_-L Avatar answered Sep 29 '22 09:09

J-_-L


Generally, it's a bad idea to define a class as "class A" but then "magically" make it contained by module B. If you want to refer to class A as B::A, you should define it using either:

module B
  class A
    # contents
  end
end

or:

class B::A
  # contents
end

Otherwise anyone who reads your code will be confused. In this case, you don't gain anything in clarity, brevity, or convenience by using "tricks", so straightforward code is better. There is a lesson here: the metaprogramming features of Ruby are great, but there is no need to use them gratuitously. Only use them when you really gain something from doing so. Otherwise you just make your code hard to understand.

BUT, having read your comment, it looks like there is really a good reason to do something like this in your case. I suggest that the following solution would be even better than what you are envisioning:

m = Module.new
m.module_eval("class C; end")
m.constants
=> [:C]
m.const_get(:C)
=> #<Module:0xfd0da0>::C

You see? If you want a "guaranteed unique" namespace, you can use an anonymous module. You could store these modules in a hash or other data structure, and pull the classes out of them as needed. This solves the problem you mentioned, that the users of your app are going to be adding their own classes, and you don't want the names to collide.

like image 22
Alex D Avatar answered Sep 29 '22 08:09

Alex D