code introspection

D

Daniel Cremer

Hi,

Is there a solution to do code introspection rather than introspection
at the level of the Module/Object ? I can't figure out if I'm looking
for something that isn't there in Ruby since I've been reading C# code
or if I'm missing something. Let me explain my problem.
I would like to load ruby source files and dynamically create objects
from classes located inside these sources. It's one class/object per
source file and I can make it so that a string from a configuration
file holds the name of the class. However I can't figure out a good
way to use that string to get to the class in the source file.

I could use eval but am really uncomfortable with that for obvious
reasons (people keep saying it's evil so I don't play with him). The
other solution I came up with was to add a method in the same source
but outside the class to return the desired object:

--------------source file---------
class MyNewClass
....
end

def create_loaded_class()
return MyNewClass.new()
end
---------------------------------

Then as I require the source files I can successively invoke the
create_loaded_class method. This works but can get quite messy as
there are a lot of things to consider if you need to reload sources
and create new objects etc.
Please tell me I'm missing something obvious :).

thanks,
Daniel
 
G

gabriele renzi

il 26 Jul 2004 03:19:36 -0700, (e-mail address removed) (Daniel Cremer)
ha scritto::
I would like to load ruby source files and dynamically create objects
from classes located inside these sources. It's one class/object per
source file and I can make it so that a string from a configuration
file holds the name of the class. However I can't figure out a good
way to use that string to get to the class in the source file.

something like:

'Myclass' in 'Myclass.rb' or the file names are not related
I could use eval but am really uncomfortable with that for obvious
reasons (people keep saying it's evil so I don't play with him). The
other solution I came up with was to add a method in the same source
but outside the class to return the desired object:


you may use const_get:=> #<Foo:0x28b3eb8>
 
L

Lothar Scholz

Hello Daniel,

DC> Is there a solution to do code introspection rather than introspection
DC> at the level of the Module/Object ? I can't figure out if I'm looking

Yes but only from the C level. You need to create the "Node" tree and
travere it. Unfortuneatly there seems to be no visitor pattern class to do
this and you must expect that the Node structure can change even
between minor updates a lot. So read the code in eval.c and parse.c.

Look at the ExErb project for an application that does this.

Programming is easy if you are an experienced programmer but
maintainance is quite some work.
 
R

Robert Klemme

Lothar Scholz said:
Hello Daniel,

DC> Is there a solution to do code introspection rather than introspection
DC> at the level of the Module/Object ? I can't figure out if I'm looking

Yes but only from the C level. You need to create the "Node" tree and
travere it. Unfortuneatly there seems to be no visitor pattern class to do
this and you must expect that the Node structure can change even
between minor updates a lot. So read the code in eval.c and parse.c.

Err, I think this is overkill. Daniel just wants to create an instance of
a class whose name he knows. So the already suggested Object.const_get(
class_name ) or ObjectSpace.const_get( class_name ) suffices.

If you don't know class names beforehand, you can work with an anonymous
module as wrapper like this:

mod = Module.new
mod.class_eval( File.read( "foo.rb" ) )
new_classes = mod.constants.inject([]) {|ar,c| cl=mod.const_get(c); ar <<
cl if Class === cl; ar}
p new_classes
include mod
p Foo

Or even do this

mod = Object
mod.class_eval( File.read( "foo.rb" ) )
classes = mod.constants.inject([]) {|ar,c| cl=mod.const_get(c); ar << cl
if Class === cl; ar}
p classes
p Foo


Regards

robert
 
A

Ara.T.Howard

Hi,

Is there a solution to do code introspection rather than introspection
at the level of the Module/Object ? I can't figure out if I'm looking
for something that isn't there in Ruby since I've been reading C# code
or if I'm missing something. Let me explain my problem.
I would like to load ruby source files and dynamically create objects
from classes located inside these sources. It's one class/object per
source file and I can make it so that a string from a configuration
file holds the name of the class. However I can't figure out a good
way to use that string to get to the class in the source file.

I could use eval but am really uncomfortable with that for obvious
reasons (people keep saying it's evil so I don't play with him). The
other solution I came up with was to add a method in the same source
but outside the class to return the desired object:

--------------source file---------
class MyNewClass
...
end

def create_loaded_class()
return MyNewClass.new()
end
---------------------------------

Then as I require the source files I can successively invoke the
create_loaded_class method. This works but can get quite messy as
there are a lot of things to consider if you need to reload sources
and create new objects etc.
Please tell me I'm missing something obvious :).

thanks,
Daniel


~ > cat c.rb
files = %w(a.rb b.rb)

files.each do |file|
load file
name, klass = Common.introspect file
p name
p klass
klass.new
end


~ > ruby c.rb
"Foo"
Common::Foo
"foo"
"Bar"
Common::Bar
"bar"


~ > cat a.rb
module Common
class Foo
def initialize
p 'foo'
end
end

class << self
def introspect path
@introspect[File.basename(path)]
end
end
@introspect = {} unless defined? @introspect
@introspect[File.basename(__FILE__)] = 'Foo', Foo
end


~ > cat b.rb
module Common
class Bar
def initialize
p 'bar'
end
end

class << self
def introspect path
@introspect[File.basename(path)]
end
end
@introspect = {} unless defined? @introspect
@introspect[File.basename(__FILE__)] = 'Bar', Bar
end


you could obviously do this without the 'Common' module - but i think it's
cleaner/safer this way - provided you can manage to wrap all your modules this
way...

kind regards.

-a
--
===============================================================================
| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE :: 303.497.6469
| A flower falls, even though we love it;
| and a weed grows, even though we do not love it.
| --Dogen
===============================================================================
 
D

Daniel Cremer

Lothar Scholz said:
Hello Daniel,

DC> Is there a solution to do code introspection rather than introspection
DC> at the level of the Module/Object ? I can't figure out if I'm looking

Yes but only from the C level. You need to create the "Node" tree and
travere it. Unfortuneatly there seems to be no visitor pattern class to do
this and you must expect that the Node structure can change even
between minor updates a lot. So read the code in eval.c and parse.c.

Err, I think this is overkill. Daniel just wants to create an instance of
a class whose name he knows. So the already suggested Object.const_get(
class_name ) or ObjectSpace.const_get( class_name ) suffices.

If you don't know class names beforehand, you can work with an anonymous
module as wrapper like this:

mod = Module.new
mod.class_eval( File.read( "foo.rb" ) )
new_classes = mod.constants.inject([]) {|ar,c| cl=mod.const_get(c); ar <<
cl if Class === cl; ar}
p new_classes
include mod
p Foo

(...)

ahh... it looks like creating an anonymous module is the piece that is
really missing for me at the moment...
thanks for the ideas, I'm not sure how far I'll have to carry this so
they're all appreciated

-Daniel
 
J

Joel VanderWerf

Using module_eval in the context of a wrapper module is one way to go.
(I know you said no eval, but load/require end up eval-ing, anyway. I
guess you could do something with $SAFE if you are worried about mischief.)

This has gotten so common for me that I put it in a small lib:
http://redshift.sourceforge.net/script. Here's a way of using it for
what you want to do:

--- program.rb ---


require 'script'

files = [ "dog.rb", "cat.rb" ]

files.each do |file|
animal_wrapper = Script.load(file)
# animal_wrapper is a Module in whose context the top-level
# constants and methods are defined.

p animal_wrapper.main_file # should be same as the file local var
p animal_wrapper::CLASS # top-level constant in the script
p animal_wrapper.make_animal # top-level meth in the script
end


--- dog.rb ---


class Dog
def initialize name
@name = name
end
end

CLASS = Dog

def make_animal
Dog.new "Rover"
end


--- cat.rb ---


class Dog
def initialize name
@name = name
end
end

CLASS = Dog

def make_animal
Dog.new "Rover"
end


--- output of program.rb ---


"/home/vjoel/tmp/script-example/dog.rb"
#<Script:0x401c4e3c>::Dog
#<#<Script:0x401c4e3c>::Dog:0x401c4964 @name="Rover">
"/home/vjoel/tmp/script-example/cat.rb"
#<Script:0x401c4928>::Cat
#<#<Script:0x401c4928>::Cat:0x401c4450 @name="Fuzzy">

--------

(You might want Dog and Cat to inherit from some base class that has a
self.to_s method to avoid the hex junk.)

You could simplify the picture by defining the _same_ class in each
script file:

class Animal
def initialize;...;end
end

and then you can reference it by

animal_wrapper::Animal.new

in your main program. The fact that dog.rb and cat.rb are loaded into
different wrapper contexts keeps these classes distinct, even though
their names appear the same.
 
J

Joel VanderWerf

Joel said:
--- cat.rb ---


class Dog
def initialize name
@name = name
end
end

CLASS = Dog

def make_animal
Dog.new "Rover"
end

Ooops. Copy/paste error.

--- cat.rb ---


class Cat
def initialize name
@name = name
end
end

CLASS = Cat

def make_animal
Cat.new "Fuzzy"
end
 
G

gabriele renzi

il Mon, 26 Jul 2004 23:56:40 +0900, Daniel Cremer
If you don't know class names beforehand, you can work with an anonymous
module as wrapper like this:

mod = Module.new
mod.class_eval( File.read( "foo.rb" ) )
new_classes = mod.constants.inject([]) {|ar,c| cl=mod.const_get(c); ar <<
cl if Class === cl; ar}
p new_classes
include mod
p Foo

(...)

ahh... it looks like creating an anonymous module is the piece that is
really missing for me at the moment...
thanks for the ideas, I'm not sure how far I'll have to carry this so
they're all appreciated

well, than take a look at the simple load:

load(filename, wrap=false) => true
-----------------------------------------------------------------------
Loads and executes the Ruby program in the file _filename_. If the
filename does not resolve to an absolute path, the file is
searched
for in the library directories listed in +$:+. If the optional
_wrap_ parameter is +true+, the loaded script will be executed
under an anonymous module, protecting the calling program's global
namespace. In no circumstance will any local variables in the
loaded file be propagated to the loading environment.
 
R

Robert Klemme

gabriele renzi said:
il Mon, 26 Jul 2004 23:56:40 +0900, Daniel Cremer
If you don't know class names beforehand, you can work with an anonymous
module as wrapper like this:

mod = Module.new
mod.class_eval( File.read( "foo.rb" ) )
new_classes = mod.constants.inject([]) {|ar,c| cl=mod.const_get(c); ar <<
cl if Class === cl; ar}
p new_classes
include mod
p Foo

(...)

ahh... it looks like creating an anonymous module is the piece that is
really missing for me at the moment...
thanks for the ideas, I'm not sure how far I'll have to carry this so
they're all appreciated

well, than take a look at the simple load:

load(filename, wrap=false) => true
-----------------------------------------------------------------------
Loads and executes the Ruby program in the file _filename_. If the
filename does not resolve to an absolute path, the file is
searched
for in the library directories listed in +$:+. If the optional
_wrap_ parameter is +true+, the loaded script will be executed
under an anonymous module, protecting the calling program's global
namespace. In no circumstance will any local variables in the
loaded file be propagated to the loading environment.

Yes, but that doesn't help here since there's no way to find this
anonymous module in order to find all classes defined in the file. I
tried that initially, too, but the return value of load is not the module
but 'true' or 'false'. Or am I missing somethig?

Regards

robert
 
J

Joel VanderWerf

Robert said:
Yes, but that doesn't help here since there's no way to find this
anonymous module in order to find all classes defined in the file. I
tried that initially, too, but the return value of load is not the module
but 'true' or 'false'. Or am I missing somethig?

The anonymous module is not even reachable, so it will get GC-ed, as in
this example:

irb(main):002:0> def foo; load "f.rb", true; end
=> nil
irb(main):003:0> ObjectSpace.each_object(Module){}
=> 372
irb(main):004:0> foo
=> true
irb(main):005:0> ObjectSpace.each_object(Module){}
=> 373
irb(main):006:0> foo
=> true
irb(main):007:0> ObjectSpace.each_object(Module){}
=> 374
irb(main):008:0> GC.start
=> nil
irb(main):009:0> ObjectSpace.each_object(Module){}
=> 372

Getting one's hands on the wrapper module is the raison d'etre of my
little "script" lib (http://redshift.sourceforge.net/script). More
simply, you can just read the file and use module_eval (but script adds
some features).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,787
Messages
2,569,627
Members
45,328
Latest member
66Teonna9

Latest Threads

Top