I have a script that iterates using ObjectSpace#each_object
with no args. Then it prints how many instances exist for each class.
I realized that some classes redefine the #class
instance method, so I had to find another way to get the actual class; Let's say it's stored in variable "klass"
, and klass === object
is true.
In Ruby 1.8 I could do this, assuming Object
wasn't monkeypatched:
Object.instance_method(:class).bind(object).call
This worked for ActiveSupport::Duration
instances:
# Ruby 1.8 # (tries to trick us) 20.seconds.class => Fixnum # don't try to trick us, we can tell Object.instance_method(:class).bind(20.seconds).call => ActiveSupport::Duration
But, in Ruby 1.9 this no longer works:
# Ruby 1.9 # we are not smart... Object.instance_method(:class).bind(20.seconds).call TypeError: bind argument must be an instance of Object from (irb):53:in `bind' from (irb):53 from /Users/user/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'
It turns out that ActiveSupport::Duration
subclasses ActiveSupport::BasicObject
. The latter is made to subclass ::BasicObject
in Ruby 1.9, so Object
is excluded from the inheritance chain. This doesn't, and can't, happen in Ruby 1.8, so ActiveSupport::BasicObject
is a subclass of Object
.
I haven't found any way to detect the actual class of a Ruby 1.9 object that isn't an instance of Object
. BasicObject
in 1.9 is really bare-bones:
BasicObject.instance_methods => [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__]
Ideas?
UPDATE:
Since ruby 1.9 reached end-of-life, I'm changing my accept to @indirect's answer. The mentions of ruby 1.9 above are merely for historical purposes, to show that the change from 1.8 to 1.9 was the original cause of my problem.
The following solution refers to the superclass of the eigenclass. As a consequence, it has the side effect of allocating the eigenclass (detectable by ObjectSpace.count_objects[:T_CLASS]
in MRI). But since BasicObject#class
is only invoked on blank slate objects (i.e. objects that are not kind-of Object
, i.e. that are not Object
s) the side effect also applies just for blank slate objects. For Object
s, the standard Kernel#class
is invoked.
class BasicObject def class (class << self; self end).superclass end end # tests: puts RUBY_VERSION # 1.9.2 class B < BasicObject; end class X; end p BasicObject.new.class # BasicObject p B .new.class # B p X .new.class # X p 6.class # Fixnum p B.instance_method(:class).owner # BasicObject p X.instance_method(:class).owner # Kernel p 6.method(:class).owner # Kernel
Edit - Note: Indeed, there is an issue with ActiveSupport::Duration
. This class uses interception (method_missing
) for redirecting messages to the :value
attribute. As a consequence, it provides false introspection for its instances. To preserve this falsity, it is necessary to use another name for the class map, e.g. the proposed __realclass__
. Thus, the modified solution might look like this:
class BasicObject def __realclass__; (class << self; self end).superclass end end class Object; alias __realclass__ class end
Another way of not invoking class << self
on Object
s is via Module#===
, as suggested by Kelvin on this page.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With