19

Is there a way of checking whether an object has a singleton class without creating one?

Other than stated in Check if an object has a singleton class, it is not true that every object has a singleton class (see proof under https://repl.it/DuVJ/2).

The following approaches came into my mind, but don't work:

  1. obj.singleton_class

    This automatically creates a new singleton class if none exists (see https://ruby-doc.org/core-1.9.2/Object.html#method-i-singleton_class).

  2. Using ObjectSpace:

    has_singleton_class = ObjectSpace.each_object(Class).any? do |klass|
      klass < self.class && klass.singleton_class? && self.is_a?(klass)
    end
    

    This is very slow and might not work under jRuby as ObjectSpace might not be available.

  3. obj.singleton_methods only works if the singleton class has at least one method.

Community
  • 1
  • 1
sudoremo
  • 2,274
  • 2
  • 22
  • 39
  • 2
    What problem are you trying to solve? As Jörg wrote in the linked answer, "Whenever you go looking for a singleton class, it'll be there." What's your use case? – Jordan Running Oct 12 '16 at 16:43
  • We're working on https://github.com/rails/rails/pull/26771. The problem is that when we're adding unintended classes via `singleton_class` which breaks some of the Rails tests that check the class count. We'd like to go through `self.singleton_class` if it exists, and otherwise call the method on `self.class`. – sudoremo Oct 12 '16 at 16:50
  • 7
    My opinion is: `ObjectSpace` lets you look behind the curtain. But if you do that, you might see things that you aren't supposed to see. I stand by my assertion on the linked question: singleton classes always exist. The interpreter may possibly only *physically* create them when they are needed, but everytime you look to *check* whether there is one, there *will* be one. YARV, for example, it always creates singleton classes for modules and classes, regardless of whether they are needed or not, and it never creates them for other objects, unless you actually open it or add a singleton method. – Jörg W Mittag Oct 12 '16 at 17:05
  • I think you're right. I still don't see any way of working around this at the moment though. – sudoremo Oct 12 '16 at 17:10
  • 7
    The real solution might be to change those tests that check the class count to work in a different way. – Jordan Running Oct 12 '16 at 19:58
  • @Remo are you still looking for a solution – BenKoshy Feb 21 '17 at 05:13
  • @BKSpurgeon To be honest, no. The fix within Rails seems to be a big thing and we have changed our code to not rely on thread safety with class attributes. Thank you though! – sudoremo Feb 23 '17 at 10:50
  • @Remo nevertheless it will make an interesting study for me. I'll have to look into it. – BenKoshy Feb 23 '17 at 12:23
  • @BKSpurgeon Very cool, thank you! – sudoremo Feb 23 '17 at 13:47
  • 1
    There is a method in ruby vm which called as `rb_singleton_class_get` which returns nil in case there is no singleton class for the object but unfortunately it's an internal method. – Foo Bar Zoo Mar 13 '18 at 22:08
  • Your problem/question is probably solved/answered by Amadan's answer to this question https://stackoverflow.com/questions/53644285/inspecting-marshal-methods – Min-Soo Pipefeet Dec 08 '18 at 18:20

3 Answers3

3

An unelegant way I found out is to try:

Marshal.dump obj

If obj has a singleton class (without custom serialization strategy), we get a TypeError:

TypeError: singleton can't be dumped
Min-Soo Pipefeet
  • 2,208
  • 4
  • 12
  • 31
2

You can use the ancestors method. Because you want to check if a class (not an object) is a singleton, you can fetch all the modules mixed within the class and verify if any of those are a singleton class.

class Klass
  include Singleton
end

Klass.ancestors.include? Singleton # true

In ruby, for creating a singleton you should include a Singleton module. So if you check for that module it means that that class is a singleton. Ruby's base class inheritance of the module class meaning that you have access to use the ancestors method.

References:

  1. https://ruby-doc.org/core-2.1.0/Module.html#method-i-ancestors
  2. https://ruby-doc.org/core-2.2.0/Class.html
  3. https://ruby-doc.org/stdlib-2.1.0/libdoc/singleton/rdoc/Singleton.html#module-Singleton-label-Usage
Roy Cruz
  • 79
  • 2
  • 1
    Thanks for your answer. Contrary to common sense, this issue is not about the singleton pattern but about an advanced ruby feature / interna called "Eigenclass" which, unfortunately, is also called "Singleton class". This basically is a class that exists just for one single instance and can differ to the "real" instance class. While your answer, in itself, is perfectly correct, I'm afraid it isn't related to the subject at hand. – sudoremo May 08 '17 at 17:12
0

The ancestors method returns an ordered list of classes and modules that corresponds with the method lookup sequence. below example, instances of the class CheckKlass will search for methods in CheckKlass, Object, Kernel, and BasicObject before calling A#method_missing.

class CheckKlass; end
CheckKlass.ancestors # => [CheckKlass, Object, Kernel, BasicObject]

And if Singleton is included this would look like below

class CheckKlass
  include Singleton
end

This could give you =>

[CheckKlass, Singleton, ActiveSupport::ToJsonWithActiveSupportEncoder, Object, PP::ObjectMixin, ActiveSupport::Dependencies::Loadable, JSON::Ext::Generator::GeneratorMethods::Object, ActiveSupport::Tryable, Kernel, BasicObject]
  • Thanks for your answer. But again, this question has nothing to do with the singleton pattern or the `Singleton` module but with a ruby concept called *Eigenclass*. See my comment on the answer to Roy Cruz for more information. I know it's confusing ;) – sudoremo Mar 13 '18 at 10:00