Mimic another Ruby class so the object passes the === type check

Viewed 1426

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?

2 Answers
Related