Nifty one-liner to search ObjectSpace?


#1

Say I’ve got a class and I know it has subclasses. I’m absolutely sure
there must be an irb ObjectSpace one-liner to give me all the
subclasses of an arbitrary class.

The best I have right now is based on a solution to something else, a
Scriptaculous inline autocompleter for Rails apps that gives you Ajaxy
autocomplete search of all class names within your Rails app.

http://gilesbowkett.com/blog_code_samples/123006_seaside_rails/pat.txt

I’ve been able to find the Class instance method #inherited(), which
takes another class and tells you yay or nay, but a simple one-liner
to return a class’s subclasses has so far proved out of my reach.

I know it exists. I just don’t know what it is. I’m also kind of
stymied because it appears ObjectSpace has #each_object but not #each.
That seems so weird to me.


#2

On Tue, Mar 06, 2007, Giles B. wrote:

Say I’ve got a class and I know it has subclasses. I’m absolutely sure
there must be an irb ObjectSpace one-liner to give me all the
subclasses of an arbitrary class.

Yup :slight_smile:

I’ve been able to find the Class instance method #inherited(), which
takes another class and tells you yay or nay, but a simple one-liner
to return a class’s subclasses has so far proved out of my reach.

ObjectSpace.each_object( Class ) {|klass| puts klass if klass < IO }

…will give you any class that is a subclass of IO. Could pretty
easily be wrapped into a method you could call on a class. In fact:

class Class
def subclasses
out = []
ObjectSpace.each_object( Class ) {|k| out << k if k < self }
return out
end
end

Stick that in your .irbrc and call IO.subclasses. YEAH.

I know it exists. I just don’t know what it is. I’m also kind of
stymied because it appears ObjectSpace has #each_object but not #each.
That seems so weird to me.

What would #each do that #each_object doesn’t?

Ben


#3

On 3/5/07, Giles B. removed_email_address@domain.invalid wrote:

http://www.gilesgoatboy.org
http://gilesbowkett.blogspot.com
http://giles.tumblr.com/

How about
ObjectSpace.each_object {|o| puts o.name if o.is_a?(Class) && o <
BaseClass}

irb(main):005:0> ObjectSpace.each_object {|o| puts o.name if
o.is_a?(Class) && o < Numeric}
Bignum
Float
Fixnum
Integer

Pat


#4

On Tue, Mar 06, 2007, Pat M. wrote:

How about
ObjectSpace.each_object {|o| puts o.name if o.is_a?(Class) && o < BaseClass}

ObjectSpace.each_object takes an optional argument that restricts it to
a given class/module, so you can take out the #is_a? check by saying
ObjectSpace.each_object( Class ) {}

Ben


#5

On 3/5/07, Ben B. removed_email_address@domain.invalid wrote:

end
Ben

#each enables #collect, doesn’t it? It’s the core iterator for all
those other iterator methods.

I wanted to throw the horns all metal-style when I first saw this, but
it doesn’t work in irb:

class Class
def subclasses
out = []
ObjectSpace.each_object( Class ) {|k| out << k if k < self }
return out
end
end
=> nil

Event.subclasses
NoMethodError: protected method subclasses' called for Event:Class from /opt/local/lib/ruby/gems/1.8/gems/activerecord-1.15.2/lib/active_record/base.rb:1236:inmethod_missing’
from (irb):9

Event.new.subclasses
NoMethodError: undefined method subclasses' for #<Event:0x3556b50> from /opt/local/lib/ruby/gems/1.8/gems/activerecord-1.15.2/lib/active_record/base.rb:1861:inmethod_missing’
from (irb):10

I thought maybe I needed to use Class.class_eval instead:

Class.class_eval do
def subclasses
out = []
ObjectSpace.each_object( Class ) {|k| out << k if k < self }
return out
end
end

but I got identical error output when I tried that. In fact I think I
just used a synonym for “class Class” by using “Class.class_eval” and
the whole thing was very cargo cult of me.

Anyway, that < operator for inheritance is insanely cool, but I’m
missing something getting this to work.

Couldn’t get Pat’s solution to work either!

ObjectSpace.each_object {|o| puts o.name if o.is_a?(Class) && o < Event}
=> 55128

Maybe just molasses in the brainpan today.


#6

On Tue, Mar 06, 2007, Giles B. wrote:

#each enables #collect, doesn’t it? It’s the core iterator for all
those other iterator methods.

Good call, hadn’t considered that.

I wanted to throw the horns all metal-style when I first saw this, but
it doesn’t work in irb:

WORKSFORME:

class Class
def subclasses
out = []
ObjectSpace.each_object( Class ) {|k| out << k if k < self }
return out
end
end
=> nil

IO.subclasses
=> [File, Socket, UNIXServer, UNIXSocket, UDPSocket, TCPServer,
TCPSocket, IPSocket, BasicSocket]

I just tried this on a Rails model, and it bombed. I think that’s
what’s happening to you. You can do Event.send :subclasses, but that’s
sort of ghetto.

but I got identical error output when I tried that. In fact I think I
just used a synonym for “class Class” by using “Class.class_eval” and
the whole thing was very cargo cult of me.

hehehe

Couldn’t get Pat’s solution to work either!

ObjectSpace.each_object {|o| puts o.name if o.is_a?(Class) && o < Event}
=> 55128

This doesn’t make sense to me, but without being in your environment
it’s useless to try to help :slight_smile:

Ben


#7

muppet = []
=> []

ObjectSpace.each_object {|o| muppet.push(o.name) if o.is_a?(Class)
&& o < Event}
=> 57244

muppet
=> []


#8

The Rails problem is due to some Rails internals thing, I think. I
just got it to work using #subklasses instead of #subclasses for the
method name. Doesn’t fix the lazy loading, though.


#9

You know what, I’m sorry, I figured out why this didn’t work.
ObjectSpace only contains the classes loaded, not the classes which
could be loaded. The subclasses of the class I was examining hadn’t
been loaded yet.

ObjectSpace.each_object(Class){|k| out << k if k < ActiveRecord::Base}
=> 1355

out
=> [CGI::Session::ActiveRecordStore::Session, Event, Tag, Tagging]

The Fixnum returned by #each_object() still baffles me, but I think
for this to be useful I’d have to autoload all the classes defined in
the app; I was doing this in Rails console and apparently the console
lazily loads only the things it needs at any given time.


#10

On Tue, Mar 06, 2007, Giles B. wrote:

The Fixnum returned by #each_object() still baffles me, but I think
for this to be useful I’d have to autoload all the classes defined in
the app; I was doing this in Rails console and apparently the console
lazily loads only the things it needs at any given time.

I’m pretty sure it’s the total number of objects inspected by the call
to each_object. So that’d mean it looked at 1355 classes to find the
tasty nuggets it was after.

Ben


#11

Sorry, that was my assumption too, I’m just puzzled that it’s doing
that, rather than returning an array or something.


#12

Giles B. schrieb:

Say I’ve got a class and I know it has subclasses. I’m absolutely sure
there must be an irb ObjectSpace one-liner to give me all the
subclasses of an arbitrary class.
(…)
I know it exists. I just don’t know what it is. I’m also kind of
stymied because it appears ObjectSpace has #each_object but not #each.
That seems so weird to me.

Giles, here’s something for both of your wishes:

require “enumerator”

s = Exception # substitute with your superclass
puts ObjectSpace.to_enum(:each_object, class<<s; self; end).to_a

Note that you can replace “to_a” with any method of Enumerable.

Regards,
Pit


#13

On 06.03.2007 01:21, Giles B. wrote:

I’ve been able to find the Class instance method #inherited(), which
takes another class and tells you yay or nay, but a simple one-liner
to return a class’s subclasses has so far proved out of my reach.

I know it exists. I just don’t know what it is. I’m also kind of
stymied because it appears ObjectSpace has #each_object but not #each.
That seems so weird to me.

Additional note: if you need the subclass info often you may want to
look into #inherited. With that method you can record all subclasses of
a class when they are created and do not need to revisit all classes.

Kind regards

robert