Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieve a Ruby object from its singleton class?

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.

like image 509
Eric Duminil Avatar asked Dec 17 '22 18:12

Eric Duminil


2 Answers

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>
>
like image 198
Stefan Avatar answered Jan 06 '23 06:01

Stefan


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]
like image 42
Aleksei Matiushkin Avatar answered Jan 06 '23 05:01

Aleksei Matiushkin