Class: Matchi::BeAnInstanceOf

Inherits:
Object
  • Object
show all
Defined in:
lib/matchi/be_an_instance_of.rb

Overview

Type matcher that checks if an object is an exact instance of a specific class.

This matcher provides a secure way to verify an object’s exact type, ensuring it matches a specific class without including subclasses. It uses Ruby’s method binding mechanism to bypass potential method overrides, providing better protection against type check spoofing than standard instance_of? checks.

Examples:

Basic usage

matcher = Matchi::BeAnInstanceOf.new(String)
matcher.match? { "test" }      # => true
matcher.match? { :test }       # => false

Inheritance behavior

class Animal; end
class Dog < Animal; end

matcher = Matchi::BeAnInstanceOf.new(Animal)
matcher.match? { Animal.new }  # => true
matcher.match? { Dog.new }     # => false  # Subclass doesn't match

Secure type checking

# Consider a class that attempts to masquerade as String:
class MaliciousString
  def class; String; end
  def instance_of?(klass); true; end
end

obj = MaliciousString.new
obj.instance_of?(String)                           # => true (spoofed)

matcher = Matchi::BeAnInstanceOf.new(String)
matcher.match? { obj }                             # => false (secure)

Different ways to specify the class

# Using class directly
Matchi::BeAnInstanceOf.new(String)

# Using class name as string
Matchi::BeAnInstanceOf.new("String")

# Using class name as symbol
Matchi::BeAnInstanceOf.new(:String)

# Using namespaced class
Matchi::BeAnInstanceOf.new("MyModule::MyClass")

See Also:

Instance Method Summary collapse

Constructor Details

#initialize(expected) ⇒ BeAnInstanceOf

Initialize the matcher with (the name of) a class or module.

Examples:

BeAnInstanceOf.new(String)          # Using class
BeAnInstanceOf.new("String")        # Using string
BeAnInstanceOf.new(:String)         # Using symbol

Parameters:

  • expected (Class, #to_s)

    The expected class or its name Can be provided as a Class object, String, or Symbol

Raises:

  • (ArgumentError)

    if the class name doesn’t start with an uppercase letter



69
70
71
72
73
74
75
# File 'lib/matchi/be_an_instance_of.rb', line 69

def initialize(expected)
  @expected = String(expected)
  return if /\A[A-Z]/.match?(@expected)

  raise ::ArgumentError,
        "expected must start with an uppercase letter (got: #{@expected})"
end

Instance Method Details

#match? { ... } ⇒ Boolean

Securely checks if the yielded object is an instance of the expected class.

This method uses Ruby’s method binding mechanism to get the true class of an object, bypassing potential method overrides. While not completely foolproof, it provides better protection against type check spoofing than using regular method calls which can be overridden.

Examples:

Simple type check

matcher = BeAnInstanceOf.new(String)
matcher.match? { "test" }        # => true
matcher.match? { StringIO.new }  # => false

Yields:

  • Block that returns the object to check

Yield Returns:

  • (Object)

    The object to verify the type of

Returns:

  • (Boolean)

    true if the object’s actual class is exactly the expected class

Raises:

  • (ArgumentError)

    if no block is provided

  • (NameError)

    if the expected class cannot be found



98
99
100
101
102
103
# File 'lib/matchi/be_an_instance_of.rb', line 98

def match?
  raise ::ArgumentError, "a block must be provided" unless block_given?

  actual_class = ::Object.instance_method(:class).bind_call(yield)
  expected_class == actual_class
end

#to_sString

Returns a human-readable description of the matcher.

Examples:

BeAnInstanceOf.new(String).to_s # => "be an instance of String"

Returns:

  • (String)

    A string describing what this matcher verifies



113
114
115
# File 'lib/matchi/be_an_instance_of.rb', line 113

def to_s
  "be an instance of #{@expected}"
end