From c3268a5bd207bebba3931ee6abc4cc2879bd8d06 Mon Sep 17 00:00:00 2001 From: "Daniel (dB.) Doubrovkine" Date: Sat, 15 Aug 2026 13:06:54 -0400 Subject: [PATCH] Fix values, value? and key returning a superclass' overridden value (#62) values, value? and key each had their own logic for merging a subclass' enums with its superclass', instead of using the memoized, deduping _enum_hash used by to_h, keys, key?, value, parse and each. values did a recursive list concatenation (superclass.values + result), so a subclass redefining a parent's key showed both the parent's stale value and its own override. value? and key relied on _enums_by_value, a hash merged by value rather than by key, so a superclass' stale value=>enum mapping for an overridden key was never overwritten (it's a different hash key), leaving value?/key returning true/the key for values that no longer belong to that key. Fixed by: * values now reads from _enum_hash like every other method. * _enums_by_value is now derived from _enum_hash (keyed by key) instead of merged directly by value, so overridden keys can't leave stale by-value entries behind. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 1 + lib/ruby-enum/enum.rb | 24 +++++++++++------------- spec/ruby-enum/enum_spec.rb | 11 +++++++++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff45070..91e04fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### 1.2.1 (Next) +* [#62](https://github.com/dblock/ruby-enum/issues/62): Fixed `values`, `value?` and `key` to not return a superclass' overridden value when a subclass redefines a key, matching the behavior of `to_h` and other methods - [@dblock](https://github.com/dblock). * Your contribution here. ### 1.2.0 (2026/8/14) diff --git a/lib/ruby-enum/enum.rb b/lib/ruby-enum/enum.rb index 70ded84..b9ef27e 100644 --- a/lib/ruby-enum/enum.rb +++ b/lib/ruby-enum/enum.rb @@ -124,15 +124,9 @@ def keys _enum_hash.values.map(&:key) end - # Returns all enum values. + # Returns all enum values, including those defined in a superclass. def values - result = _own_enum_hash.values.map(&:value) - - if superclass < Ruby::Enum - superclass.values + result - else - result - end + _enum_hash.values.map(&:value) end # Iterate over all enumerated values, including those defined in a superclass. @@ -186,12 +180,16 @@ def _enum_hash # Returns the enums-by-value hash for this class merged with all of its # superclasses, with values defined in this class taking precedence over # those inherited from a superclass. + # + # Derived from _enum_hash (keyed by key) rather than merged directly by + # value, so that a superclass' stale value for a key overridden in this + # class doesn't linger, e.g. when a subclass redefines a key with a new + # value, the superclass' old value should no longer be found via value? + # or key. def _enums_by_value - @_enums_by_value ||= if superclass < Ruby::Enum - superclass.send(:_enums_by_value).merge(_own_enums_by_value) - else - _own_enums_by_value - end + @_enums_by_value ||= _enum_hash.each_with_object({}) do |(_key, enum), hash| + hash[enum.value] = enum + end end def upper?(s) diff --git a/spec/ruby-enum/enum_spec.rb b/spec/ruby-enum/enum_spec.rb index dc64fdd..ac7513b 100644 --- a/spec/ruby-enum/enum_spec.rb +++ b/spec/ruby-enum/enum_spec.rb @@ -432,8 +432,15 @@ class EmptyEnums expect(SubclassRedefiningParentKey.to_h).to eq(RED: 'crimson', GREEN: 'green') end - it 'includes both the parent and its own redefined value in values' do - expect(SubclassRedefiningParentKey.values).to eq(%w[red green crimson]) + it "does not include the parent's overridden value, only its own redefined value, in values" do + expect(SubclassRedefiningParentKey.values).to eq(%w[crimson green]) + end + + it "does not recognize the parent's overridden value via value? or key" do + expect(SubclassRedefiningParentKey.value?('red')).to be false + expect(SubclassRedefiningParentKey.key('red')).to be_nil + expect(SubclassRedefiningParentKey.value?('crimson')).to be true + expect(SubclassRedefiningParentKey.key('crimson')).to eq :RED end it "the parent class' own value is unaffected" do