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
If you can upgrade to Ruby 2.0, you don't need to implement anything at all:
>> Kernel.instance_method(:class).bind(BasicObject.new).call
=> BasicObject
I don't know about doing it in Ruby, but this is straightforward using the C API to Ruby. The RubyInline Gem makes adding bits of C to your Ruby code quite easy:
require 'inline'
class Example
inline do |builder|
builder.c_raw_singleton <<SRC, :arity => 1
VALUE true_class(VALUE self, VALUE to_test) {
return rb_obj_class(to_test);
}
SRC
end
end
And then:
1.9.2p180 :033 > Example.true_class(20.minutes)
=> ActiveSupport::Duration
The following code creates a BasicKernel
module via duplication of the Kernel
module and subsequent removal of all methods except the class
method. The BasicKernel
is included into the BasicObject
class (just like Kernel
is included into Object
).
In req_methods
, you can specify arbitrary subset of Kernel
methods to be preserved.
class BasicObject
include ::BasicKernel = ::Kernel.dup.module_eval {
v = $VERBOSE
$VERBOSE = nil # suppress object_id warning
req_methods = [:class] # required methods (to be preserved)
all_methods = public_instance_methods +
protected_instance_methods +
private_instance_methods
all_methods.each { |x| remove_method(x) unless req_methods.include?(x) }
$VERBOSE = v
self
}
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 B.instance_method(:class).owner # BasicKernel
p X.instance_method(:class).owner # Kernel
p Object.ancestors # [Object, Kernel, BasicObject, BasicKernel]
p BasicKernel.instance_methods # [:class]
Edit: See the Note in https://stackoverflow.com/a/10216927/641718
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.
fguillen's link made me think of this way.
Pros:
Cons:
.
class BasicObject
def self.inherited(klass)
klass.send(:define_method, :__realclass__) { klass }
end
def __realclass__
BasicObject
end
end
# ensures that every Object will also have this method
class Object
def __realclass__
Object.instance_method(:class).bind(self).call
end
end
require 'active_support/core_ext'
20.seconds.__realclass__ # => ActiveSupport::Duration
# this doesn't raise errors, so it looks like all objects respond to our method
ObjectSpace.each_object{|e| e.__realclass__ }
This is my modification of @paon's answer:
Reasoning behind the changes:
ActiveSupport::Duration
instance behavior 2.seconds.class
remains
Fixnum
.Object
doesn't have its own __realclass__
method, we want to avoid allocating the eigenclass for those instances. @paon's original answer did this inherently by defining the class
method name.class BasicObject
def __realclass__
::Object === self ?
# Note: to be paranoid about Object instances, we could
# use Object.instance_method(:class).bind(s).call.
self.class :
(class << self; self end).superclass
end
end
# test
require 'active_support/core_ext/integer'
require 'active_support/core_ext/numeric'
duration = 2.seconds
string = 'hello world'
p duration.class # => Fixnum
p string.class # => String
GC.start
p ObjectSpace.count_objects[:T_CLASS] # => 566
# creates the eigenclass
p duration.__realclass__ # => ActiveSupport::Duration
p ObjectSpace.count_objects[:T_CLASS] # => 567
# doesn't create the eigenclass
p string.__realclass__ # => String
p ObjectSpace.count_objects[:T_CLASS] # => 567