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
|
module Dry
# Build an equalizer module for the inclusion in other class
#
# @api public
def self.Equalizer(*keys, **options)
Dry::Equalizer.new(*keys, **options)
end
# Define equality, equivalence and inspection methods
class Equalizer < Module
# Initialize an Equalizer with the given keys
#
# Will use the keys with which it is initialized to define #cmp?,
# #hash, and #inspect
#
# @param [Array<Symbol>] keys
# @param [Hash] options
# @option options [Boolean] :inspect whether to define #inspect method
# @option options [Boolean] :immutable whether to memoize #hash method
#
# @return [undefined]
#
# @api private
def initialize(*keys, **options)
@keys = keys.uniq
define_methods(**options)
freeze
end
private
# Hook called when module is included
#
# @param [Module] descendant
# the module or class including Equalizer
#
# @return [self]
#
# @api private
def included(descendant)
super
descendant.include Methods
end
# Define the equalizer methods based on #keys
#
# @param [Boolean] inspect whether to define #inspect method
# @param [Boolean] immutable whether to memoize #hash method
#
# @return [undefined]
#
# @api private
def define_methods(inspect: true, immutable: false)
define_cmp_method
define_hash_method(immutable: immutable)
define_inspect_method if inspect
end
# Define an #cmp? method based on the instance's values identified by #keys
#
# @return [undefined]
#
# @api private
def define_cmp_method
keys = @keys
define_method(:cmp?) do |comparator, other|
keys.all? do |key|
__send__(key).public_send(comparator, other.__send__(key))
end
end
private :cmp?
end
# Define a #hash method based on the instance's values identified by #keys
#
# @return [undefined]
#
# @api private
def define_hash_method(immutable:)
calculate_hash = ->(obj) { @keys.map { |key| obj.__send__(key) }.push(obj.class).hash }
if immutable
define_method(:hash) do
@__hash__ ||= calculate_hash.call(self)
end
define_method(:freeze) do
hash
super()
end
else
define_method(:hash) do
calculate_hash.call(self)
end
end
end
# Define an inspect method that reports the values of the instance's keys
#
# @return [undefined]
#
# @api private
def define_inspect_method
keys = @keys
define_method(:inspect) do
klass = self.class
name = klass.name || klass.inspect
"#<#{name}#{keys.map { |key| " #{key}=#{__send__(key).inspect}" }.join}>"
end
end
# The comparison methods
module Methods
# Compare the object with other object for equality
#
# @example
# object.eql?(other) # => true or false
#
# @param [Object] other
# the other object to compare with
#
# @return [Boolean]
#
# @api public
def eql?(other)
instance_of?(other.class) && cmp?(__method__, other)
end
# Compare the object with other object for equivalency
#
# @example
# object == other # => true or false
#
# @param [Object] other
# the other object to compare with
#
# @return [Boolean]
#
# @api public
def ==(other)
other.is_a?(self.class) && cmp?(__method__, other)
end
end
end
end
|