Find the fully qualified name of a class from a string


#1

Say you have a class definition in a string -

str = <<EOF
module A
class B
def b
puts “hello”
end
end
end
EOF

What is the easiest way to find out the fully qualified name of the
class
there, namely “A::B”

Now I could do a thing like

module EmptyModule
end

EmptyModule.module_eval(str)

def self.find_class mod
mod.constants.each do |str|
m = mod.const_get(str)
if m.class == Module
find_class(m)
else
cname = m.to_s
puts cname.match(/EmptyModule::/).post_match
break
end
end
end

and call this method

find_class(EmptyModule)

which prints “A::B”

This works but it seems too much work to do something simple.

Is there a better/simpler way to do this?

Thanks
Nasir


#2

On Sat, 31 Mar 2007, Nasir K. wrote:

EOF

 end

Is there a better/simpler way to do this?

Thanks
Nasir

harp:~ > cat a.rb

a better const_get

def constant_get(hierachy)
ancestors = hierachy.split(%r/::/)
parent = Object
while((child = ancestors.shift))
klass = parent.const_get child
parent = klass
end
klass
end

c = constant_get ‘A::B’

-a


#3

On 3/30/07, removed_email_address@domain.invalid removed_email_address@domain.invalid wrote:

end

    break

This works but it seems too much work to do something simple.
def constant_get(hierachy)
ancestors = hierachy.split(%r/::/)
parent = Object
while((child = ancestors.shift))
klass = parent.const_get child
parent = klass
end
klass
end

c = constant_get ‘A::B’

I don’t think that’s what he’s looking for.

He has a string containing Ruby source code which presumably defines a
class (or classes?) which might be contained within a module. He
want’s to find the fully qualified name of this unknown class (or
classes?).

The A::B is really just an example.

WHY he wants to do this is beyond me.


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/


#4

I don’t think that’s what he’s looking for.

Thank you.

He has a string containing Ruby source code which presumably defines a

class (or classes?) which might be contained within a module. He
want’s to find the fully qualified name of this unknown class (or
classes?).

Yes.

The A::B is really just an example.

WHY he wants to do this is beyond me.

Huh! Then ask me.

Anyway, there could be several uses for such a thing. Simplest use case
would be a RAILS like (but not RAILS) situation where a controller is
deployed on a running server which (controller) optionally comes as a
string
enveloped in a protocol message, this string is evaled and the
controller is
instatiated but the system also needs to maintain meta information
including
the name of the controller just deployed, which defaults to the fully
qualified name of the class.
My use case is similar.

  • Nasir


#5

On Sat, Mar 31, 2007 at 05:54:42AM +0900, Nasir K. wrote:

EOF

What is the easiest way to find out the fully qualified name of the class
there, namely “A::B”

Interesting. I thought that in general you couldn’t:

m = Module.new
Foo = m
Bar = m
module m

what’s the name of the module we are in now??

end

But actually Ruby rejects that, so you must say ‘module Foo’ or ‘module
Bar’


#6

On 3/31/07, Nasir K. removed_email_address@domain.invalid wrote:

Anyway, there could be several uses for such a thing. Simplest use case
would be a RAILS like (but not RAILS) situation where a controller is
deployed on a running server which (controller) optionally comes as a string
enveloped in a protocol message, this string is evaled and the controller is
instatiated but the system also needs to maintain meta information including
the name of the controller just deployed, which defaults to the fully
qualified name of the class.
My use case is similar.

Here’s one way to capture that info as it’s evaled. Not thread-safe
as-is. Not even sure if it’s useful at all, but I was a bit curious.
There’s more ways to do this, of course, especially depending on when
you want to capture this info.

str = <<EOF
module A
class B
def b
puts “hello”
end
end
end
EOF

set_trace_func proc {|event, file, line, id, binding, classname|
return unless event == ‘class’
name = eval(‘self.class == Class && Module.nesting[0]’, binding)
if name
# Do something with name
end
}

eval(str)

set_trace_func(nil)


#7

On 3/30/07, Kristoffer Lundén removed_email_address@domain.invalid wrote:

end

}

eval(str)

set_trace_func(nil)

I like his idea of evaluating the string in the context of a ‘sandbox’
module, here’s a refinement which does that. It strips off the
throwaway module since I assume that he’s doing this to pre-flight
check actually re-running the code again after scanning it.

I also check to see if the trace function is running on the
originating thread, there are still thread safety issues since another
thread might also be using set_trace_func. Another way to approach
this would be to set and reset Thread.critical, but it doesn’t solve
the second problem since there doesn’t seem to be a way to stack trace
functions.

rick@bill:/public/rubyscripts$ cat find_classes.rb
def classes_defined_in(str)
begin
this_thread = Thread.current
result = []
set_trace_func(lambda do |event, file, line, id, binding, classname|
return unless Thread.current == this_thread
return unless event == ‘class’
name = eval(‘self.class == Class && Module.nesting[0]’,binding)
result << name.to_s.split(/::/,2)[1] if name
end)
ensure
Module.new.module_eval(str)
set_trace_func(nil)
end
result
end

str = <<EOF
module M1
class C1
def c
puts “hello”
end
end

module M2
class C2
end
end
end
module M3
class C3
end
end
EOF

p classes_defined_in(str)
rick@bill:/public/rubyscripts$ ruby find_classes.rb
[“M1::C1”, “M1::M2::C2”, “M3::C3”]


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/


#8

On 3/31/07, Brian C. removed_email_address@domain.invalid wrote:

end
module m

what’s the name of the module we are in now??

end

But actually Ruby rejects that, so you must say ‘module Foo’ or ‘module Bar’

I think this illustrates your point:

m = Module.new do
attr_accessor :name
end

B = Class.new do
include m
end
p B.ancestors

[B, #<Module:0xb7c3bc2c>, Object, Kernel]

Regards,

Sean


#9

Here is an abridged (out of context) version of my original solution.
(which
I am using now)

nasir@sparkle:misc>cat find_class_name.rb

module EmptyModule
def EmptyModule.clear_all
constants.each {|x| remove_const(x.to_sym)}
end
end

@my_classes = []

def class_from_string(str)
EmptyModule.clear_all # clean the slate
EmptyModule.module_eval(str)
end

cstr = <<EOF
module B
class A
def a
puts “hello”
end
end
end
module B
class C
end
class D
end
end
EOF

This string will usually come from some external invocation of this

function

class_from_string(cstr)

def find_class mod
mod.constants.each do |str|
m = mod.const_get(str)
if m.class == Module
find_class(m)
else
cname = m.to_s
cname = cname.match(/EmptyModule::/).post_match
@my_classes << cname
end
end
end

find_class( EmptyModule )
puts @my_classes.uniq


And the result -

nasir@sparkle:misc>ruby find_class_name.rb
B::smiley:
B::C
B::A


I guess I will stick with this solution. set_trace_func usage is very
interesting but as I could have potentially several such evaluations
going
on in parallel, I would go with the solution above.

Comments criticisms welcome.

~ nasir


#10

On 3/31/07, Nasir K. removed_email_address@domain.invalid wrote:

def a

EOF
if m.class == Module
find_class( EmptyModule )


I guess I will stick with this solution. set_trace_func usage is very
interesting but as I could have potentially several such evaluations going
on in parallel, I would go with the solution above.

Comments criticisms welcome.

This is still not thread safe since if you are doing this on two
different threads they are sharing the EmptyModule.

Here’s my variation using your approach but with an anonymous module
to do the module_eval:

rick@frodo:/public/rubyscripts$ cat find_classes2.rb
class String
def all_class_names
mod = Module.new
mod.module_eval(self)
mod.all_class_names
end
end

class Module
def all_class_names
class_names = []
constants.each do |const_name|
const = const_get(const_name)
case const
when Class
class_names << const.to_s.split(/::/,2)[1]
when Module
class_names += const.all_class_names
end
end
class_names.uniq
end
end

cstr = <<EOF
module B
class A
def a
puts “hello”
end
end
end

module B
class C
end
class D
end

NonClassConst = :non_class_const
end
EOF

puts cstr.all_class_names
rick@frodo:/public/rubyscripts$ ruby find_classes2.rb
B::C
B::A
B::smiley:


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/


#11

Good. This is better.

Thanks
~nasir