1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
# frozen_string_literal: true
require 'equatable/version'
# Make it easy to define equality and hash methods.
module Equatable
# Hook into module inclusion.
#
# @param [Module] base
# the module or class including Equatable
#
# @return [self]
#
# @api private
def self.included(base)
super
base.extend(self)
base.class_eval do
include Methods
define_methods
end
end
# Holds all attributes used for comparison.
#
# @return [Array<Symbol>]
#
# @api private
attr_reader :comparison_attrs
# Objects that include this module are assumed to be value objects.
# It is also assumed that the only values that affect the results of
# equality comparison are the values of the object's attributes.
#
# @param [Array<Symbol>] *args
#
# @return [undefined]
#
# @api public
def attr_reader(*args)
super
comparison_attrs.concat(args)
end
# Copy the comparison_attrs into the subclass.
#
# @param [Class] subclass
#
# @api private
def inherited(subclass)
super
subclass.instance_variable_set(:@comparison_attrs, comparison_attrs.dup)
end
private
# Define all methods needed for ensuring object's equality.
#
# @return [undefined]
#
# @api private
def define_methods
define_comparison_attrs
define_compare
define_hash
define_inspect
end
# Define class instance #comparison_attrs as an empty array.
#
# @return [undefined]
#
# @api private
def define_comparison_attrs
instance_variable_set('@comparison_attrs', [])
end
# Define a #compare? method to check if the receiver is the same
# as the other object.
#
# @return [undefined]
#
# @api private
def define_compare
define_method(:compare?) do |comparator, other|
klass = self.class
attrs = klass.comparison_attrs
attrs.all? do |attr|
other.respond_to?(attr) && send(attr).send(comparator, other.send(attr))
end
end
end
# Define a hash method that ensures that the hash value is the same for
# the same instance attributes and their corresponding values.
#
# @api private
def define_hash
define_method(:hash) do
klass = self.class
attrs = klass.comparison_attrs
([klass] + attrs.map { |attr| send(attr) }).hash
end
end
# Define an inspect method that shows the class name and the values for the
# instance's attributes.
#
# @return [undefined]
#
# @api private
def define_inspect
define_method(:inspect) do
klass = self.class
name = klass.name || klass.inspect
attrs = klass.comparison_attrs
"#<#{name}#{attrs.map { |attr| " #{attr}=#{send(attr).inspect}" }.join}>"
end
end
# The equality methods
module Methods
# Compare two objects for equality based on their value
# and being an instance of the given class.
#
# @param [Object] other
# the other object in comparison
#
# @return [Boolean]
#
# @api public
def eql?(other)
instance_of?(other.class) && compare?(__method__, other)
end
# Compare two objects for equality based on their value
# and being a subclass of the given class.
#
# @param [Object] other
# the other object in comparison
#
# @return [Boolean]
#
# @api public
def ==(other)
other.is_a?(self.class) && compare?(__method__, other)
end
end # Methods
end # Equatable
|