Retrieve a Ruby object from its singleton class?
Asked Answered
T

4

7

It is possible to access a singleton class from a Ruby object with:

some_object.singleton_class

Is it possible to do the reverse operation : access the original object when inside the singleton class?

class << some_object
  # how to reference some_object without actually typing some_object?
end

I wanted to DRY this method:

class Example
  PARENTS = []
  class << PARENTS
    FATHER = :father
    MOTHER = :mother
    PARENTS.push(FATHER, MOTHER)
  end
end

and tried to replace PARENTS inside the class with something more generic.

Tetra answered 5/2, 2019 at 9:32 Comment(2)
There's also singleton_class? so a singleton class is quite aware of its special status.Gosh
"without actually typing some_object" – it's even worse: if some_object is a local variable, it's not defined in the class << some_object block. Attempting to reference it within the block results in a NameError.Gosh
B
2

Class#attached_object (Ruby 3.2+)

Starting from Ruby 3.2, there is a Class#attached_object method:

Returns the object for which the receiver is the singleton class.

Raises an TypeError if the class is not a singleton class.

For example:

class Foo; end

Foo.singleton_class.attached_object     #=> Foo
Foo.attached_object                     #=> TypeError: `Foo' is not a singleton class
Foo.new.singleton_class.attached_object #=> #<Foo:0x000000010491a370>
TrueClass.attached_object               #=> TypeError: `TrueClass' is not a singleton class
NilClass.attached_object                #=> TypeError: `NilClass' is not a singleton class

Sources:

Bidding answered 24/5, 2023 at 18:56 Comment(1)
Excellent, thanks! I just tested it. It seems to work fine with attached_object.push(FATHER, MOTHER).Tetra
G
7

I'm not aware of any built-in method or keyword but you could write a method that adds a (singleton) method to an object's singleton class, returning the object itself:

class Object
  def define_instance_accessor(method_name = :instance)
    singleton_class.define_singleton_method(method_name, &method(:itself))
  end
end

Usage:

obj = Object.new              #=> #<Object:0x00007ff58e8742f0>
obj.define_instance_accessor
obj.singleton_class.instance  #=> #<Object:0x00007ff58e8742f0>

In your code:

class Example
  PARENTS = []
  PARENTS.define_instance_accessor
  class << PARENTS
    FATHER = :father
    MOTHER = :mother
    instance.push(FATHER, MOTHER)
  end
end

Internally, YARV stores the object in an instance variable called __attached__. The instance variable doesn't have the usual @ prefix, so it isn't visible or accessible from within Ruby.

Here's a little C extension to expose it:

#include <ruby.h>

static VALUE
instance_accessor(VALUE klass)
{
    return rb_ivar_get(klass, rb_intern("__attached__"));
}

void Init_instance_accessor()
{
    rb_define_method(rb_cClass, "instance", instance_accessor, 0);
}

Usage:

irb -r ./instance_accessor
> obj = Object.new
#=> #<Object:0x00007f94a11e1260>
> obj.singleton_class.instance
#=> #<Object:0x00007f94a11e1260>
>
Gosh answered 5/2, 2019 at 9:59 Comment(1)
Excellent work with the C extension. I'm still hoping there's a a clean-ish plain Ruby solution, though.Tetra
I
2

Just out of curiosity (please don’t use at home or school)

object = []
class << object
  type, id = to_s[/(?<=:#<).*?(?=>)/].split(':')
  ObjectSpace.each_object(Kernel.const_get(type)).find do |e|
    e.__id__ == id.to_i(16) >> 1
  end << :father
end   
#⇒ [:father]
Insomnia answered 5/2, 2019 at 10:20 Comment(1)
@MarcinKołodziej yeah, it’s “how to turn all your teammates into violent enemies in 24 hours” :)Insomnia
S
2

We can do that as follows.

def singleton_class_to_object(sc)
  ObjectSpace.each_object(Object).find { |o|
    (o.singleton_class == sc) rescue false }
end

o = Object.new
  #=> #<Object:0x00005b52e502d030> 
singleton_class_to_object(o.singleton_class)
  #=> #<Object:0x00005b52e502d030> 

class C; end
singleton_class_to_object(C.singleton_class)
  #=> C

The in-line rescue is to deal with objects o that are immediate objects, having no singleton classes.

In MRI v2.7.0,

ObjectSpace.each_object(Object).to_a.size
  #=> 35362

a mere pittance.

Slumlord answered 12/2, 2019 at 19:13 Comment(4)
Good idea. It's still not completely straightforward but it goes in the right direction, and without needing C.Tetra
It works fine with the original example : pastebin.com/DqJh9JuB Do you have any better idea for method name?Tetra
For the name, how about ssalc_notelgnis? I'm not sure you want to check if the argument is a singleton class within the method, but if you do perhaps raise an exception if it isn't. (Consider a guard clause rather than if: raise ... unless singleton_class.) Also, shouldn't that be defined in Class rather than Module?Slumlord
Another idea is singleton_class_of_what?.Slumlord
B
2

Class#attached_object (Ruby 3.2+)

Starting from Ruby 3.2, there is a Class#attached_object method:

Returns the object for which the receiver is the singleton class.

Raises an TypeError if the class is not a singleton class.

For example:

class Foo; end

Foo.singleton_class.attached_object     #=> Foo
Foo.attached_object                     #=> TypeError: `Foo' is not a singleton class
Foo.new.singleton_class.attached_object #=> #<Foo:0x000000010491a370>
TrueClass.attached_object               #=> TypeError: `TrueClass' is not a singleton class
NilClass.attached_object                #=> TypeError: `NilClass' is not a singleton class

Sources:

Bidding answered 24/5, 2023 at 18:56 Comment(1)
Excellent, thanks! I just tested it. It seems to work fine with attached_object.push(FATHER, MOTHER).Tetra

© 2022 - 2024 — McMap. All rights reserved.