I want to create an object that acts as a specific class, such as Fixnum, but isn’t an instance of that class nor its subclasses.
There are various use-cases for this. In the Fixnum case, I want to define a more specific integer type that is essentially a Fixnum but implements some extra logic, too. I can’t subclass Fixnum itself because immediate types such as Fixnum and Symbol can’t be subclassed.
Another use-case is mocking in automated tests: sometimes you want to create an object that acts like a certain class (usually a model instance) but is for technical reasons not an instance of that exact class.
Here’s how to create a specific integer type that delegates all methods to an internally stored fixnum:
require 'delegate'
require 'forwardable'
# integer representing a page number
class PageNumber < DelegateClass(Integer)
extend Forwardable
def initialize(value, name)
@name = name
super(value)
end
def inspect
"#{@name} #{to_i}"
end
alias_method :to_i, :__getobj__
def_delegators :to_i, :instance_of?, :kind_of?, :is_a?
end
This object can pass is_a? and similar checks:
page = PageNumber.new(1, "page")
page.is_a? Fixnum #=> true
But nothing I do can make it pass the Module#=== type check:
# my problem:
Fixnum === page #=> false
The fact that my object fails this check is very unfortunate, since the === method is used internally in case statements:
case page
when Fixnum
# it will never get here
when String
# ...
else
# ...
end
My question is how can I create a mock type that passes the === check without augmenting the === methods on built-in classes?
This is a hackish solution that I warned against in my question:
It solves the problem but raises a question is it safe to do? I can’t think of any drawbacks of this method from the top of my mind but since we’re messing with a very important method here it might not be something we’d want to do.