Reputation: 54223
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.
Upvotes: 7
Views: 593
Reputation: 9238
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:
Upvotes: 2
Reputation: 110685
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.
Upvotes: 2
Reputation: 114178
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>
>
Upvotes: 7
Reputation: 121000
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]
Upvotes: 2