Forum: Ruby Find the fully qualified name of a class from a string

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
9aafe2636a115bbdc137c93445054f22?d=identicon&s=25 Nasir Khan (Guest)
on 2007-03-30 22:55
(Received via mailing list)
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
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2007-03-30 23:09
(Received via mailing list)
On Sat, 31 Mar 2007, Nasir Khan 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
8f6f95c4bd64d5f10dfddfdcd03c19d6?d=identicon&s=25 Rick Denatale (rdenatale)
on 2007-03-31 01:24
(Received via mailing list)
On 3/30/07, ara.t.howard@noaa.gov <ara.t.howard@noaa.gov> 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/
9aafe2636a115bbdc137c93445054f22?d=identicon&s=25 Nasir Khan (Guest)
on 2007-03-31 02:03
(Received via mailing list)
> 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


--
6ed4a727f8f6c71ca2f759008ec3febe?d=identicon&s=25 Kristoffer Lundén (stoffe)
on 2007-03-31 02:46
(Received via mailing list)
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)
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (Guest)
on 2007-03-31 10:49
(Received via mailing list)
On Sat, Mar 31, 2007 at 05:54:42AM +0900, Nasir Khan 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'
8f6f95c4bd64d5f10dfddfdcd03c19d6?d=identicon&s=25 Rick Denatale (rdenatale)
on 2007-03-31 17:31
(Received via mailing list)
On 3/30/07, Kristoffer Lundén <kristoffer.lunden@gmail.com> 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/
E1d641bfe4071a5413bac781f06d3fd1?d=identicon&s=25 Sean O'halpin (sean)
on 2007-03-31 17:36
(Received via mailing list)
On 3/31/07, Brian Candler <B.Candler@pobox.com> 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
9aafe2636a115bbdc137c93445054f22?d=identicon&s=25 Nasir Khan (Guest)
on 2007-03-31 22:11
(Received via mailing list)
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.

~ nasir
8f6f95c4bd64d5f10dfddfdcd03c19d6?d=identicon&s=25 Rick Denatale (rdenatale)
on 2007-04-03 03:04
(Received via mailing list)
On 3/31/07, Nasir Khan <rubylearner@gmail.com> 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::D



--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/
9aafe2636a115bbdc137c93445054f22?d=identicon&s=25 Nasir Khan (Guest)
on 2007-04-03 17:40
(Received via mailing list)
Good. This is better.

Thanks
~nasir
This topic is locked and can not be replied to.