code introspection

Discussion in 'Ruby' started by Daniel Cremer, Jul 26, 2004.

  1. 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
    Daniel Cremer, Jul 26, 2004
    #1
    1. Advertising

  2. il 26 Jul 2004 03:19:36 -0700, (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:
    >> class Foo
    >> end

    => nil
    >> Object::const_get('Foo').new

    => #<Foo:0x28b3eb8>
    gabriele renzi, Jul 26, 2004
    #2
    1. Advertising

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


    --
    Best regards, emailto: scholz at scriptolutions dot com
    Lothar Scholz http://www.ruby-ide.com
    CTO Scriptolutions Ruby, PHP, Python IDE 's
    Lothar Scholz, Jul 26, 2004
    #3
  4. "Lothar Scholz" <> schrieb im Newsbeitrag
    news:...
    > 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
    Robert Klemme, Jul 26, 2004
    #4
  5. Daniel Cremer

    Ara.T.Howard Guest

    On Mon, 26 Jul 2004, Daniel Cremer wrote:

    > 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
    ===============================================================================
    Ara.T.Howard, Jul 26, 2004
    #5
  6. On Mon, 2004-07-26 at 14:16, Robert Klemme wrote:
    > "Lothar Scholz" <> schrieb im Newsbeitrag
    > news:...
    > > 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
    Daniel Cremer, Jul 26, 2004
    #6
  7. > On Mon, 26 Jul 2004, Daniel Cremer wrote:
    >
    >> 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 :).


    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.
    Joel VanderWerf, Jul 26, 2004
    #7
  8. Joel VanderWerf wrote:
    > --- 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
    Joel VanderWerf, Jul 26, 2004
    #8
  9. il Mon, 26 Jul 2004 23:56:40 +0900, Daniel Cremer
    <> ha scritto::

    >> 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.
    gabriele renzi, Jul 27, 2004
    #9
  10. "gabriele renzi" <> schrieb im Newsbeitrag
    news:...
    > il Mon, 26 Jul 2004 23:56:40 +0900, Daniel Cremer
    > <> ha scritto::
    >
    > >> 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
    Robert Klemme, Jul 29, 2004
    #10
  11. Robert Klemme wrote:
    > "gabriele renzi" <> schrieb im Newsbeitrag
    > news:...

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


    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).
    Joel VanderWerf, Jul 29, 2004
    #11
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. David Smith
    Replies:
    0
    Views:
    309
    David Smith
    Jul 11, 2003
  2. Replies:
    6
    Views:
    261
    Bruno Desthuilliers
    Aug 27, 2006
  3. Thomas W
    Replies:
    3
    Views:
    449
    Thomas W
    Nov 28, 2006
  4. Steven T. Hatton

    Synopsis: A Source-code Introspection Tool

    Steven T. Hatton, Jul 6, 2005, in forum: C++
    Replies:
    2
    Views:
    501
    Howard
    Jul 6, 2005
  5. Alex J
    Replies:
    7
    Views:
    791
    Alex J
    Sep 12, 2011
Loading...

Share This Page