Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Given a Ruby metaclass, how do I get the instance to which it is attached?

This is the inverse of the question "Given an instance of a Ruby object, how do I get its metaclass?"

You can see a representation of the object to which a metaclass or singleton class is attached in the default to_s output:

s = "hello"
s_meta = class << s; self; end
s_meta.to_s # => "#<Class:#<String:0x15004dd>>"

class C; end
c_meta = class << C; self; end
c_meta.to_s # => "#<Class:C>"

Is it possible to implement a method Class.attached that returns this object (or nil if the receiver is a regular class)?

s_meta.attached # => s
c_meta.attached # => C
C.attached # => nil
like image 952
John Avatar asked Aug 13 '11 21:08

John


3 Answers

There's an ugly (yet working) hack, using ObjectSpace. Like, something that you should never use except for playing and perhaps debugging. You just want its first (and only) instance, so:

ObjectSpace.each_object(self).first

To determine whether it's a singleton class, you can use the weird property that ancestors will not include its receiver if it's a singleton class (or eigenclass, or magical class):

ObjectSpace.each_object(self).first unless ancestors.include? self

If you care about edgecases, there are three objects whose classes are also their singleton classes.

[true, false, nil].each do |o|
   o.class.send(:define_method, :attached) { o }
 end
like image 192
Mon ouïe Avatar answered Nov 08 '22 09:11

Mon ouïe


I don't know about MRI.

In JRuby, the following returns what you want:

require 'java'
class A
  def self.meta
    class << self; self; end
  end
end

A.meta.to_java.attached
like image 43
Confusion Avatar answered Nov 08 '22 11:11

Confusion


You can get it from inspect (in MRI implementation):

class Class
  def attached
    # first, match the object reference from inspect
    o_ref = inspect.match /0x([0-9a-f]+)>>$/

    # if not found, it's not a metaclass
    return nil unless o_ref

    # calculate the object id from the object reference    
    o_id = (o_ref[1].to_i(16) >> 1) - 0x80000000

    # get the object from its id
    ObjectSpace._id2ref o_id
  end
end

# testing...
class A; end

a = A.new 
a_meta = class << a; self; end

p a                        #=> #<A:0xb7507b00>
p a_meta                   #=> #<Class:#<A:0xb7507b00>>
p a_meta.attached          #=> #<A:0xb7507b00>
p a == a_meta.attached     #=> true
p A.attached               #=> nil

For the relationship between object id and inspect, see this answer.

like image 1
Sony Santos Avatar answered Nov 08 '22 10:11

Sony Santos