Overridding singleton classes of instances
In Ruby, most types allow you to open up their singleton classes to override instance methods:
class SomeType
def value
"some_type"
end
end
a = SomeType.new
puts a.value
# => some_type
class << a
def value
"singleton_class"
end
end
puts a.value
# => singleton_class
s = "symbol"
class << s
def to_sym
:lobmys
end
end
puts s.to_sym.inspect
# => :lobmys
Immediate values
Some objects are implemented as immediate values in Ruby, and cannot be implemented have singleton methods defined on them directly:
Fixnum
,true
,nil
, andfalse
are implemented as immediate values. With immediate values, variables hold the objects themselves, rather than references to them.Singleton methods cannot be defined for such objects. Two
Fixnums
of the same value always represent the same object instance, so (for example) instance variables for theFixnum
with the value1
are shared between all the1
’s in the system. This makes it impossible to define a singleton method for just one of these.
DelegateClass wrapper
If you have a use case for extending the behavior of an immediate type, you can create a DelegateClass
-wrapped subclass of that type and define the method on the subclass:
require "delegate"
class IntegerProxy < DelegateClass(Integer)
def |(other)
print "#{self.to_s(2)} | #{other.to_s(2)} => "
IntegerProxy.new(super).tap { |new_value| puts new_value.to_s(2) }
end
end
The above code lets you dump every |
operation and its outcome. One thing to note is the IntegerProxy.new(super)
which creates an instance of the wrapped immediate value for the new result. One downside of wrapping immediate values is that you no longer share the same space for two different instances of the same value, but this may make for more efficient debugging than a full trace function.
select_thing=IntegerProxy.new(0)
select_thing |= 0b1001
# => 0 | 1001 => 1001
select_thing |= 0b10010
# => 1001 | 10010 => 11011
Trying to see where a method is defined
Another useful thing if you have overriding of methods that may overlap is being able to tell where the version that you’re calling was defined. You can do this by substituting method(:method_name).source_location
for method_name
wherever you would normally invoke the method:
not_proxied=0
puts "select_thing.method(:|).source_location # => #{select_thing.method(:|).source_location}"
# => select_thing.method(:|).source_location # => ["integer_proxy_test.rb", 53]
puts "not_proxied.method(:|).source_location #=> #{0.method(:|).source_location.inspect}"
# => not_proxied.method(:|).source_location #=> nil
Note that the immediate type’s method (and if I recall correctly, any implementation that’s in C code) returns nil
. However, the DelegateClass
method returns the source file and line number of its definition.
Conclusion
Both DelegateClass
and .method(symbol).source_location
are probably not going to go into your production code, but as the source code and the problems get more complex to filter through, you may find usefulness in reaching for them to prove or disprove your debugging theories.