Last active
March 14, 2024 22:12
-
-
Save xunker/6c92e3fff3d81a8245caf2ee4994f884 to your computer and use it in GitHub Desktop.
Ruby IntegerWithIndifferentComparison - An experiment to show how to make an Integer-like class in Ruby without subclassing the Integer class directly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# IntegerWithIndifferentComparison | |
# -------------------------------- | |
# https://gist.github.com/xunker/6c92e3fff3d81a8245caf2ee4994f884 | |
# | |
# An experiment to show how to make an Integer-like class in Ruby without | |
# subclassing the Integer class directly. | |
# | |
# Instances of this class will behave like an Integer, but will allow direct | |
# equality comparison with Strings and other objects without explicit casting. | |
# | |
# Examples: | |
# > i = IntegerWithIndifferentComparison.new(2) | |
# > [2, '2', 2.0, "2.0", Rational(4,2)].all?{|other| i == other } | |
# > # => true | |
# > | |
# > i == '3' | |
# > # => false | |
# | |
# Nitty-Gritty | |
# ============ | |
# | |
# You're not supposed to subclass Integer directly, it says so in the docs: | |
# https://ruby-doc.org/core-2.7.1/Numeric.html: | |
# | |
# > There can only ever be one instance of the integer 1, for example. Ruby | |
# > ensures this by preventing instantiation. If duplication is attempted, the | |
# > same instance is returned. | |
# > | |
# > For this reason, Numeric should be used when defining other numeric | |
# > classes. | |
# > | |
# > Classes which inherit from Numeric must implement coerce, which returns a | |
# > two-member Array containing an object that has been coerced into an | |
# > instance of the new class and self (see coerce). | |
# > | |
# > Inheriting classes should also implement arithmetic operator methods (+, -, | |
# > * and /) and the <=> operator (see Comparable). These methods may rely on | |
# > coerce to ensure interoperability with instances of other numeric classes. | |
# | |
# In this class, we **DON'T** implement the arithmetic operators _or_ #coerce. | |
# Instead, subclass `Numeric` and then we use #method_missing to delate all of | |
# those methods to an Integer (#to_i) version of ourselves. | |
class IntegerWithIndifferentComparison < Numeric | |
attr_reader :value | |
def initialize(val) | |
@value = val.to_i | |
end | |
# We want this to behave like an integer, but we're not supposed to subclass Integer directly. | |
# Use method missing to redirect everything to our Integer instance variable instead | |
def method_missing(method_name, *args, &block) | |
if @value.respond_to?(method_name) | |
@value.send(method_name, *args, &block) | |
else | |
super | |
end | |
end | |
def respond_to_missing?(method_name, include_private = false) | |
@value.respond_to?(method_name) || super | |
end | |
# :== is an alias for :eql? | |
def eql?(other) | |
@value == other.to_i | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment