equalizer 0.0.11 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: dcbcd58301e2a2cef28808c325df7a4c51eb7714
4
- data.tar.gz: e0790eb41a358a41e568eb3393e35fb424c1abc5
2
+ SHA256:
3
+ metadata.gz: 152edf98048a51f0c55c1f028dc3b4a6fe6322910eb397a3f9d7a9f0e37c6b1b
4
+ data.tar.gz: 42ba9d636346bf0fdbfe8bdf216b1a56c5f25847c3afdf91afbaea3740e97b1b
5
5
  SHA512:
6
- metadata.gz: 6ef3745470a9bb334dfaa746c0246d4255f960fdcd9b4bac246ebcac5865e241ac415831a092c6997aec276db2fb0d77e8009fac76b46e694233ae99b0c92077
7
- data.tar.gz: dc186fa2bd0482794f6f63c84db4f6bddee1eaf4c696ab8bae031e44e4656c1800430bc73b1ef1aeeb776b4343a8ec079e78efb4b85e09edb44f42c2c818a07b
6
+ metadata.gz: 4c599000f12a99e99fcf8f55a64dbf6075032c5e74ab3af5ab02dc0bc2152cfb3b7148c81ea43cd3850250eb4e4f7b3e2ccd32fc61c66e8faf2f52da6bac183a
7
+ data.tar.gz: 3a5919fb70d6b203eb574913abd78d65e943d2cfa7a5ea4d1582cfb87a99bca4876effc233f477b804d561338236050de18648ea65b10a369a5cdbe558f8eecb
data/.mutant.yml ADDED
@@ -0,0 +1,24 @@
1
+ usage: opensource
2
+ integration:
3
+ name: minitest
4
+ includes:
5
+ - lib
6
+ - test
7
+ requires:
8
+ - equalizer
9
+ - test_helper
10
+ - equalizer_test
11
+ matcher:
12
+ subjects:
13
+ - "Equalizer*"
14
+ mutation:
15
+ operators: full
16
+ timeout: 10.0
17
+ ignore_patterns:
18
+ # module_eval's __LINE__ + 1 argument only affects debugging metadata
19
+ - send{selector=module_eval}
20
+ coverage_criteria:
21
+ test_result: true
22
+ process_abort: true
23
+ environment_variables:
24
+ MUTANT: "true"
data/.rubocop.yml CHANGED
@@ -0,0 +1,28 @@
1
+ inherit_gem:
2
+ standard: config/base.yml
3
+ standard-performance: config/base.yml
4
+
5
+ plugins:
6
+ - rubocop-minitest
7
+ - rubocop-performance
8
+ - rubocop-rake
9
+
10
+ AllCops:
11
+ TargetRubyVersion: 3.3
12
+ NewCops: enable
13
+
14
+ Layout/LineLength:
15
+ Max: 80
16
+
17
+ Style/StringLiterals:
18
+ EnforcedStyle: double_quotes
19
+
20
+ Style/StringLiteralsInInterpolation:
21
+ EnforcedStyle: double_quotes
22
+
23
+ Style/Documentation:
24
+ Enabled: true
25
+
26
+ Naming/MethodParameterName:
27
+ Exclude:
28
+ - "test/**/*"
data/.yardopts ADDED
@@ -0,0 +1,4 @@
1
+ --markup markdown
2
+ --no-private
3
+ --protected
4
+ lib/**/*.rb
data/.yardstick.yml CHANGED
@@ -1,2 +1,34 @@
1
1
  ---
2
2
  threshold: 100
3
+ require_exact_threshold: false
4
+ rules:
5
+ ApiTag::Presence:
6
+ enabled: true
7
+ exclude: []
8
+ ApiTag::Inclusion:
9
+ enabled: true
10
+ exclude: []
11
+ ApiTag::ProtectedMethod:
12
+ enabled: true
13
+ exclude: []
14
+ ApiTag::PrivateMethod:
15
+ enabled: true
16
+ exclude: []
17
+ ExampleTag:
18
+ enabled: false
19
+ exclude: []
20
+ ReturnTag:
21
+ enabled: true
22
+ exclude: []
23
+ Summary::Presence:
24
+ enabled: true
25
+ exclude: []
26
+ Summary::Length:
27
+ enabled: true
28
+ exclude: []
29
+ Summary::Delimiter:
30
+ enabled: true
31
+ exclude: []
32
+ Summary::SingleLine:
33
+ enabled: true
34
+ exclude: []
data/CHANGELOG.md ADDED
@@ -0,0 +1,148 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-03-20
9
+
10
+ ### Added
11
+
12
+ - Pattern matching support via `deconstruct` and `deconstruct_keys`
13
+ - Pretty print support via `pretty_print` and `pretty_print_instance_variables`
14
+ - Input validation requiring at least one Symbol attribute
15
+ - RBS type signatures
16
+ - Steep type checking
17
+ - Descriptive module names in ancestor chain (e.g., `Equalizer(x, y)`)
18
+ - Sigstore gem signing on release
19
+
20
+ ### Changed
21
+
22
+ - Rewritten from a `Module` subclass to a plain `module` with `Equalizer.new`
23
+ returning anonymous modules
24
+ - Minimum Ruby version raised to 3.3
25
+ - `hash` method now uses `[self.class, *deconstruct].hash` for improved clarity
26
+ - `inspect` output format uses `Object#to_s` base with `@attr=value` pairs
27
+ - Uses `public_send` instead of `__send__` for attribute access
28
+ - Switched test framework from RSpec to Minitest
29
+ - Replaced `Equalizer::Methods` module with `Equalizer::InstanceMethods`
30
+ - Inlined `VERSION` constant (removed separate `version.rb` file)
31
+
32
+ ### Removed
33
+
34
+ - `coerce` support in `==` method
35
+ - `cmp?` private method (replaced with direct attribute comparisons)
36
+ - `Equalizer::Methods` module (replaced by `Equalizer::InstanceMethods`)
37
+ - devtools dependency
38
+
39
+ ## [0.0.11] - 2015-03-23
40
+
41
+ ### Changed
42
+
43
+ - Kill all surviving mutations in Equalizer
44
+ - Added mbj/devtools gem dependency
45
+ - Added bundler caching and container-based CI builds
46
+ - Fixed rspec deprecated API usage
47
+
48
+ ## [0.0.10] - 2015-03-20
49
+
50
+ ### Added
51
+
52
+ - Support for Ruby 2.1 and 2.2
53
+
54
+ ### Fixed
55
+
56
+ - Fixed use of coerce method
57
+
58
+ ### Changed
59
+
60
+ - Updated RSpec dependency to ~> 3.0
61
+ - Updated RuboCop dependency to >= 0.25
62
+ - Updated SimpleCov dependency to >= 0.9
63
+
64
+ ### Removed
65
+
66
+ - Dropped support for Ruby 1.9.2
67
+
68
+ ## [0.0.9] - 2013-12-23
69
+
70
+ ### Changed
71
+
72
+ - Changed `#hash` method to use `Array#hash` for better uniformity
73
+ - Refactored block to use a Method object
74
+ - Removed devtools as a development dependency
75
+ - Added supported Ruby versions section to README
76
+
77
+ ## [0.0.8] - 2013-12-02
78
+
79
+ ### Fixed
80
+
81
+ - Fixed mutation in equality and equivalency methods
82
+ - Fixed mutation in comparison method
83
+
84
+ ### Changed
85
+
86
+ - Refactored explicit method to be an included hook method
87
+ - Added spec for `Equalizer#included`
88
+ - Changed spec to verify private attributes can be compared
89
+
90
+ ## [0.0.7] - 2013-08-22
91
+
92
+ ### Changed
93
+
94
+ - Removed adamantium dependency
95
+ - Removed unused backports dependency
96
+ - Kill mutations in `Equalizer#define_cmp_method`
97
+ - Added license to gemspec
98
+ - Added mutant config and enabled mutation testing
99
+
100
+ ### Removed
101
+
102
+ - Stopped testing Ruby 1.8
103
+
104
+ ## [0.0.5] - 2013-03-01
105
+
106
+ ### Changed
107
+
108
+ - Bumped backports dependency to ~> 3.0, >= 3.0.3
109
+ - Bumped yard dependency to ~> 0.8.5
110
+
111
+ ## [0.0.4] - 2013-02-16
112
+
113
+ ### Changed
114
+
115
+ - Added encoding to all Ruby source files
116
+ - Upgraded gem dependencies
117
+
118
+ ## [0.0.3] - 2013-01-20
119
+
120
+ ### Fixed
121
+
122
+ - Fixed `#hash` and `#inspect` methods to have the expected arity
123
+
124
+ ## [0.0.2] - 2012-12-25
125
+
126
+ ### Changed
127
+
128
+ - Changed `#==` to have stricter behavior for subtypes
129
+
130
+ ## [0.0.1] - 2012-11-22
131
+
132
+ ### Added
133
+
134
+ - Initial release, extracted from veritas
135
+ - Define equality (`==`), equivalence (`eql?`), hashing (`hash`), and
136
+ inspection (`inspect`) methods based on specified attributes
137
+
138
+ [1.0.0]: https://github.com/dkubb/equalizer/compare/v0.0.11...v1.0.0
139
+ [0.0.11]: https://github.com/dkubb/equalizer/compare/v0.0.10...v0.0.11
140
+ [0.0.10]: https://github.com/dkubb/equalizer/compare/v0.0.9...v0.0.10
141
+ [0.0.9]: https://github.com/dkubb/equalizer/compare/v0.0.8...v0.0.9
142
+ [0.0.8]: https://github.com/dkubb/equalizer/compare/v0.0.7...v0.0.8
143
+ [0.0.7]: https://github.com/dkubb/equalizer/compare/v0.0.5...v0.0.7
144
+ [0.0.5]: https://github.com/dkubb/equalizer/compare/v0.0.4...v0.0.5
145
+ [0.0.4]: https://github.com/dkubb/equalizer/compare/v0.0.3...v0.0.4
146
+ [0.0.3]: https://github.com/dkubb/equalizer/compare/v0.0.2...v0.0.3
147
+ [0.0.2]: https://github.com/dkubb/equalizer/compare/v0.0.1...v0.0.2
148
+ [0.0.1]: https://github.com/dkubb/equalizer/releases/tag/v0.0.1
data/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- Copyright (c) 2009-2013 Dan Kubb
2
- Copyright (c) 2012 Markus Schirp (packaging)
1
+ MIT License
3
2
 
4
- Permission is hereby granted, free of charge, to any person obtaining
5
- a copy of this software and associated documentation files (the
6
- "Software"), to deal in the Software without restriction, including
7
- without limitation the rights to use, copy, modify, merge, publish,
8
- distribute, sublicense, and/or sell copies of the Software, and to
9
- permit persons to whom the Software is furnished to do so, subject to
10
- the following conditions:
3
+ Copyright (c) 2009-2026 Dan Kubb, Markus Schirp, Piotr Solnica, Erik Berlin
11
4
 
12
- The above copyright notice and this permission notice shall be
13
- included in all copies or substantial portions of the Software.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
14
11
 
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,69 +1,316 @@
1
- equalizer
2
- =========
1
+ # Equalizer
3
2
 
4
- Module to define equality, equivalence and inspection methods
3
+ [![Gem Version](https://img.shields.io/gem/v/equalizer)](https://rubygems.org/gems/equalizer)
4
+ [![Test](https://github.com/dkubb/equalizer/actions/workflows/test.yml/badge.svg)](https://github.com/dkubb/equalizer/actions/workflows/test.yml)
5
+ [![Quality](https://github.com/dkubb/equalizer/actions/workflows/quality.yml/badge.svg)](https://github.com/dkubb/equalizer/actions/workflows/quality.yml)
6
+ [![Documentation](https://github.com/dkubb/equalizer/actions/workflows/yard.yml/badge.svg)](https://github.com/dkubb/equalizer/actions/workflows/yard.yml)
7
+ [![Mutation Testing](https://github.com/dkubb/equalizer/actions/workflows/mutant.yml/badge.svg)](https://github.com/dkubb/equalizer/actions/workflows/mutant.yml)
5
8
 
6
- [![Gem Version](http://img.shields.io/gem/v/equalizer.svg)][gem]
7
- [![Build Status](http://img.shields.io/travis/dkubb/equalizer.svg)][travis]
8
- [![Dependency Status](http://img.shields.io/gemnasium/dkubb/equalizer.svg)][gemnasium]
9
- [![Code Climate](http://img.shields.io/codeclimate/github/dkubb/equalizer.svg)][codeclimate]
10
- [![Coverage Status](http://img.shields.io/coveralls/dkubb/equalizer.svg)][coveralls]
9
+ Equalizer provides equality, equivalence, hashing, pattern matching, and
10
+ inspection methods for Ruby objects based on explicitly specified attributes.
11
11
 
12
- [gem]: https://rubygems.org/gems/equalizer
13
- [travis]: https://travis-ci.org/dkubb/equalizer
14
- [gemnasium]: https://gemnasium.com/dkubb/equalizer
15
- [codeclimate]: https://codeclimate.com/github/dkubb/equalizer
16
- [coveralls]: https://coveralls.io/r/dkubb/equalizer
12
+ Unlike approaches that automatically use all `attr_reader` attributes,
13
+ Equalizer requires explicit specification of which attributes affect equality,
14
+ giving you full control over comparison behavior.
17
15
 
18
- Examples
19
- --------
16
+ ## Installation
20
17
 
21
- ``` ruby
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem "equalizer"
22
+ ```
23
+
24
+ Or install it directly:
25
+
26
+ ```bash
27
+ gem install equalizer
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```ruby
33
+ class Point
34
+ include Equalizer.new(:x, :y)
35
+
36
+ attr_reader :x, :y
37
+
38
+ def initialize(x, y)
39
+ @x = x
40
+ @y = y
41
+ end
42
+ end
43
+
44
+ p1 = Point.new(1, 2)
45
+ p2 = Point.new(1, 2)
46
+
47
+ p1 == p2 # => true
48
+ p1.eql?(p2) # => true
49
+ p1.hash == p2.hash # => true
50
+ ```
51
+
52
+ ## Features
53
+
54
+ ### Selective Attribute Comparison
55
+
56
+ Only the attributes you specify are used for equality. Other instance variables
57
+ are ignored:
58
+
59
+ > [!TIP]
60
+ > This is useful when you have attributes that shouldn't affect equality, like
61
+ > timestamps, cached values, or display names.
62
+
63
+ ```ruby
22
64
  class GeoLocation
23
65
  include Equalizer.new(:latitude, :longitude)
24
66
 
25
- attr_reader :latitude, :longitude
67
+ attr_reader :latitude, :longitude, :name
68
+
69
+ def initialize(latitude, longitude, name = nil)
70
+ @latitude = latitude
71
+ @longitude = longitude
72
+ @name = name
73
+ end
74
+ end
75
+
76
+ home = GeoLocation.new(37.7786, -122.4407, "Home")
77
+ work = GeoLocation.new(37.7786, -122.4407, "Work")
78
+
79
+ home == work # => true (name is not part of equality)
80
+ ```
81
+
82
+ ### Equality vs Equivalence
26
83
 
27
- def initialize(latitude, longitude)
28
- @latitude, @longitude = latitude, longitude
84
+ Equalizer provides two comparison methods with different semantics:
85
+
86
+ #### `==` (Equality)
87
+
88
+ Returns `true` if the other object is an instance of the same class **or a
89
+ subclass**, and all specified attributes are equal using `==`:
90
+
91
+ ```ruby
92
+ class ColoredPoint < Point
93
+ attr_reader :color
94
+
95
+ def initialize(x, y, color)
96
+ super(x, y)
97
+ @color = color
29
98
  end
30
99
  end
31
100
 
32
- point_a = GeoLocation.new(1, 2)
33
- point_b = GeoLocation.new(1, 2)
34
- point_c = GeoLocation.new(2, 2)
101
+ point = Point.new(1, 2)
102
+ colored = ColoredPoint.new(1, 2, "red")
103
+
104
+ point == colored # => true (ColoredPoint is a subclass of Point)
105
+ colored == point # => false (Point is not a subclass of ColoredPoint)
106
+ ```
107
+
108
+ > [!IMPORTANT]
109
+ > In Ruby, the `==` operator is asymmetric when comparing across class
110
+ > hierarchies. A parent class instance can equal a subclass instance, but not
111
+ > vice versa.
35
112
 
36
- point_a.inspect # => "#<GeoLocation latitude=1 longitude=2>"
113
+ #### `eql?` (Equivalence)
37
114
 
38
- point_a == point_b # => true
39
- point_a.hash == point_b.hash # => true
40
- point_a.eql?(point_b) # => true
41
- point_a.equal?(point_b) # => false
115
+ Returns `true` only if both objects are instances of the **exact same class**,
116
+ and all specified attributes are equal using `eql?`:
42
117
 
43
- point_a == point_c # => false
44
- point_a.hash == point_c.hash # => false
45
- point_a.eql?(point_c) # => false
46
- point_a.equal?(point_c) # => false
118
+ ```ruby
119
+ point = Point.new(1, 2)
120
+ colored = ColoredPoint.new(1, 2, "red")
121
+
122
+ point.eql?(colored) # => false (different classes)
123
+ colored.eql?(point) # => false (different classes)
124
+
125
+ point.eql?(Point.new(1, 2)) # => true (same class, same values)
47
126
  ```
48
127
 
49
- Supported Ruby Versions
50
- -----------------------
128
+ ### Hashing
51
129
 
52
- This library aims to support and is [tested against][travis] the following Ruby
53
- implementations:
130
+ Objects that are `eql?` will have the same hash code, making them safe for use
131
+ as Hash keys and in Sets:
132
+
133
+ > [!NOTE]
134
+ > Ruby's `Hash` and `Set` use `eql?` and `hash` together. Equalizer ensures
135
+ > these methods stay consistent—objects that are `eql?` always have matching
136
+ > hash codes.
137
+
138
+ ```ruby
139
+ require "set"
140
+
141
+ p1 = Point.new(1, 2)
142
+ p2 = Point.new(1, 2)
143
+
144
+ # As Hash keys
145
+ locations = {}
146
+ locations[p1] = "first"
147
+ locations[p2] = "second"
148
+ locations.size # => 1 (p1 and p2 are the same key)
149
+
150
+ # In Sets
151
+ set = Set.new
152
+ set << p1
153
+ set << p2
154
+ set.size # => 1
155
+ ```
156
+
157
+ ### Pattern Matching
158
+
159
+ Equalizer provides full support for Ruby's pattern matching syntax.
160
+
161
+ > [!TIP]
162
+ > Use array patterns `[x, y]` for positional matching when attribute order
163
+ > matters. Use hash patterns `{x:, y:}` for named matching when you want
164
+ > clarity or only need specific attributes.
165
+
166
+ #### Array Patterns
167
+
168
+ Use `deconstruct` for array-style pattern matching:
169
+
170
+ ```ruby
171
+ point = Point.new(3, 4)
172
+
173
+ case point
174
+ in [0, 0]
175
+ puts "origin"
176
+ in [x, 0]
177
+ puts "on x-axis at #{x}"
178
+ in [0, y]
179
+ puts "on y-axis at #{y}"
180
+ in [x, y]
181
+ puts "at (#{x}, #{y})"
182
+ end
183
+ # => "at (3, 4)"
184
+ ```
185
+
186
+ #### Hash Patterns
54
187
 
55
- * Ruby 1.8.7
56
- * Ruby 1.9.3
57
- * Ruby 2.0.0
58
- * Ruby 2.1
59
- * Ruby 2.2
60
- * [JRuby][]
61
- * [Rubinius][]
62
- * [Ruby Enterprise Edition][ree]
188
+ Use `deconstruct_keys` for hash-style pattern matching:
63
189
 
64
- [jruby]: http://jruby.org/
65
- [rubinius]: http://rubini.us/
66
- [ree]: http://www.rubyenterpriseedition.com/
190
+ ```ruby
191
+ point = Point.new(3, 4)
192
+
193
+ case point
194
+ in { x: 0, y: 0 }
195
+ puts "origin"
196
+ in { x:, y: } if x == y
197
+ puts "on diagonal at #{x}"
198
+ in { x:, y: }
199
+ puts "at (#{x}, #{y})"
200
+ end
201
+ # => "at (3, 4)"
202
+ ```
203
+
204
+ #### Class Patterns
205
+
206
+ Combine with class checks:
207
+
208
+ ```ruby
209
+ case point
210
+ in Point(x: 0, y: 0)
211
+ puts "origin point"
212
+ in Point(x:, y:)
213
+ puts "point at (#{x}, #{y})"
214
+ end
215
+ ```
216
+
217
+ ### Clean Inspect Output
218
+
219
+ Equalizer customizes `inspect` to show only the attributes used for equality:
220
+
221
+ ```ruby
222
+ class User
223
+ include Equalizer.new(:id)
224
+
225
+ attr_reader :id, :name, :email, :created_at
226
+
227
+ def initialize(id, name, email)
228
+ @id = id
229
+ @name = name
230
+ @email = email
231
+ @created_at = Time.now
232
+ end
233
+ end
234
+
235
+ user = User.new(42, "Alice", "alice@example.com")
236
+ user.inspect
237
+ # => "#<User:0x00007f... @id=42>"
238
+ # Note: name, email, and created_at are not shown
239
+ ```
240
+
241
+ > [!NOTE]
242
+ > When debugging, remember that `inspect` only shows equality attributes. Use
243
+ > `instance_variables` to see all instance variables if needed.
244
+
245
+ To keep the original `inspect` and `pretty_print` methods, pass `inspect: false`:
246
+
247
+ ```ruby
248
+ class Person < Struct.new(:id, :name)
249
+ include Equalizer.new(:id, inspect: false)
250
+ end
251
+
252
+ amy = Person.new(1, "Amy")
253
+ amy.inspect
254
+ # => "#<struct Person id=1, name=\"Amy\">"
255
+ ```
256
+
257
+ ### Clean Ancestor Chain
258
+
259
+ The included module has a descriptive name in the ancestor chain:
260
+
261
+ ```ruby
262
+ Point.ancestors
263
+ # => [Point, Equalizer(x, y), Object, Kernel, BasicObject]
264
+ ```
265
+
266
+ ## Nested Equalizer Objects
267
+
268
+ Equalizer objects can be nested and will compare correctly:
269
+
270
+ ```ruby
271
+ class Line
272
+ include Equalizer.new(:start_point, :end_point)
273
+
274
+ attr_reader :start_point, :end_point
275
+
276
+ def initialize(start_point, end_point)
277
+ @start_point = start_point
278
+ @end_point = end_point
279
+ end
280
+ end
281
+
282
+ line1 = Line.new(Point.new(0, 0), Point.new(1, 1))
283
+ line2 = Line.new(Point.new(0, 0), Point.new(1, 1))
284
+
285
+ line1 == line2 # => true
286
+ ```
287
+
288
+ ## Error Handling
289
+
290
+ > [!CAUTION]
291
+ > Equalizer validates arguments at include time. Errors will be raised
292
+ > immediately if you pass invalid arguments.
293
+
294
+ Equalizer validates its arguments:
295
+
296
+ ```ruby
297
+ # At least one attribute is required
298
+ Equalizer.new()
299
+ # => ArgumentError: at least one attribute is required
300
+
301
+ # Attributes must be Symbols
302
+ Equalizer.new("name")
303
+ # => ArgumentError: attribute must be a Symbol, got String
304
+ ```
305
+
306
+ ## Supported Ruby Versions
307
+
308
+ This library aims to support and is [tested against][test] the following Ruby
309
+ implementations:
310
+
311
+ * Ruby 3.3
312
+ * Ruby 3.4
313
+ * Ruby 4.0
67
314
 
68
315
  If something doesn't work on one of these versions, it's a bug.
69
316
 
@@ -79,20 +326,15 @@ patches in a timely fashion. If critical issues for a particular implementation
79
326
  exist at the time of a major release, support for that Ruby version may be
80
327
  dropped.
81
328
 
82
- Credits
83
- -------
329
+ [test]: https://github.com/dkubb/equalizer/actions/workflows/test.yml
330
+
331
+ ## Credits
84
332
 
85
333
  * Dan Kubb ([dkubb](https://github.com/dkubb))
86
- * Piotr Solnica ([solnic](https://github.com/solnic))
87
334
  * Markus Schirp ([mbj](https://github.com/mbj))
88
- * Erik Michaels-Ober ([sferik](https://github.com/sferik))
89
-
90
- Contributing
91
- -------------
92
-
93
- See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
335
+ * Piotr Solnica ([solnic](https://github.com/solnic))
336
+ * Erik Berlin ([sferik](https://github.com/sferik))
94
337
 
95
- Copyright
96
- ---------
338
+ ## License
97
339
 
98
- Copyright &copy; 2009-2013 Dan Kubb. See LICENSE for details.
340
+ The gem is available as open source under the terms of the [MIT License](LICENSE).