[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Re: Find the fully qualified name of a class from a string

Ara.T.Howard

3/30/2007 9:09:00 PM

3 Answers

Rick DeNatale

3/30/2007 11:24:00 PM

0

On 3/30/07, ara.t.howard@noaa.gov <ara.t.howard@noaa.gov> wrote:
> On Sat, 31 Mar 2007, Nasir Khan wrote:
>
> > 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
>
> 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'

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

Rick DeNatale

3/31/2007 3:31:00 PM

0

On 3/30/07, Kristoffer Lundén <kristoffer.lunden@gmail.com> wrote:
> On 3/31/07, Nasir Khan <rubylearner@gmail.com> 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)

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

Rick DeNatale

4/3/2007 1:04:00 AM

0

On 3/31/07, Nasir Khan <rubylearner@gmail.com> wrote:
> 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::D
> 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.

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::D



--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denh...