File: equatable.rb

package info (click to toggle)
ruby-equatable 0.6.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 160 kB
  • sloc: ruby: 394; makefile: 4
file content (149 lines) | stat: -rw-r--r-- 3,448 bytes parent folder | download
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