File: equalizer.rb

package info (click to toggle)
ruby-dry-equalizer 0.3.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 172 kB
  • sloc: ruby: 400; makefile: 4
file content (143 lines) | stat: -rw-r--r-- 3,639 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
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