6

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
Community
  • 1
  • 1
John
  • 29,546
  • 11
  • 78
  • 79

4 Answers4

8

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
Mon ouïe
  • 948
  • 5
  • 6
3

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
Confusion
  • 16,256
  • 8
  • 46
  • 71
1

You can define metaclass to store the attached object.

class Class
  attr_accessor :attached
end

class Object
  def metaclass
    meta = class << self; self; end
    meta.attached = self
    meta
  end
end

class A; end

a = A.new
a_meta = a.metaclass
p a                     #=> #<A:0xb74ed768>
p a_meta                #=> #<Class:#<A:0xb74ed768>>

obj = a_meta.attached
p obj                   #=> #<A:0xb74ed768>

puts obj == a           #=> true
p A.attached            #=> nil
Sony Santos
  • 5,435
  • 30
  • 41
1

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.

Community
  • 1
  • 1
Sony Santos
  • 5,435
  • 30
  • 41