From 1a683788c4ad514dc4074517361d478456616e61 Mon Sep 17 00:00:00 2001 From: Nazar Matus Date: Tue, 16 Jun 2026 19:22:05 +0300 Subject: [PATCH 1/4] Fix DuplicateKeyError when the whole class is reloaded --- lib/ruby-enum/enum.rb | 3 +++ spec/ruby-enum/enum_spec.rb | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/ruby-enum/enum.rb b/lib/ruby-enum/enum.rb index 0930e86..6693b66 100644 --- a/lib/ruby-enum/enum.rb +++ b/lib/ruby-enum/enum.rb @@ -19,6 +19,9 @@ def self.included(base) base.extend ClassMethods base.private_class_method(:new) + + base.instance_variable_set(:@_enum_hash, {}) + base.instance_variable_set(:@_enums_by_value, {}) end module ClassMethods diff --git a/spec/ruby-enum/enum_spec.rb b/spec/ruby-enum/enum_spec.rb index a3d811e..58bffae 100644 --- a/spec/ruby-enum/enum_spec.rb +++ b/spec/ruby-enum/enum_spec.rb @@ -229,6 +229,22 @@ class SecondSubclass < FirstSubclass end end + # rubocop:disable Rspec/DescribedClass + describe 'Reloading enum definition' do + it 'plays nice with lazy loading in Ruby on Rails' do + class_body = proc do + include Ruby::Enum + + define :BALD, 'bald' + end + + HairStyles = Class.new(&class_body) + + expect { HairStyles.class_eval(&class_body) }.not_to raise_error + end + end + # rubocop:enable Rspec/DescribedClass + describe 'Given a class that has not defined any enums' do class EmptyEnums include Ruby::Enum From 6b5b767053fd04f67fac3d0a4afee0365bbea4f2 Mon Sep 17 00:00:00 2001 From: Nazar Matus Date: Tue, 16 Jun 2026 19:44:47 +0300 Subject: [PATCH 2/4] Add test for redundant module inclusion in a subclass --- spec/ruby-enum/enum_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/ruby-enum/enum_spec.rb b/spec/ruby-enum/enum_spec.rb index 58bffae..8c32b6a 100644 --- a/spec/ruby-enum/enum_spec.rb +++ b/spec/ruby-enum/enum_spec.rb @@ -17,6 +17,13 @@ class FirstSubclass < Colors class SecondSubclass < FirstSubclass define :PINK, 'pink' end + + class OtherSecondSubclass < FirstSubclass + include Ruby::Enum + + define :MAGENTA, 'magenta' + end + it 'returns an enum value' do expect(Colors::RED).to eq 'red' expect(Colors::GREEN).to eq 'green' @@ -169,6 +176,12 @@ class SecondSubclass < FirstSubclass expect(SecondSubclass.values).to eq(%w[red green orange pink]) end end + + context 'when a subclass of a subclass is defined with redundant module inclusion' do + it 'returns all values' do + expect(OtherSecondSubclass.values).to eq(%w[red green orange magenta]) + end + end end describe '#to_h' do From c57c56c30e0ed5e1807add0a1ebcca0b96a76066 Mon Sep 17 00:00:00 2001 From: Nazar Matus Date: Wed, 17 Jun 2026 13:37:24 +0300 Subject: [PATCH 3/4] rubocop: disable RSpec/DescribedClass rule + nits --- .rubocop.yml | 5 ++++- spec/ruby-enum/enum_spec.rb | 6 ++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a71c181..558e59a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -11,7 +11,7 @@ Metrics/BlockLength: RSpec/SpecFilePathFormat: Enabled: false -Style/LineLength: +Layout/LineLength: Enabled: false Style/HashEachMethods: @@ -38,6 +38,9 @@ RSpec/NestedGroups: RSpec/ExampleLength: Enabled: false +RSpec/DescribedClass: + Enabled: false + RSpec/MultipleExpectations: Enabled: false diff --git a/spec/ruby-enum/enum_spec.rb b/spec/ruby-enum/enum_spec.rb index 8c32b6a..c650c31 100644 --- a/spec/ruby-enum/enum_spec.rb +++ b/spec/ruby-enum/enum_spec.rb @@ -242,13 +242,12 @@ class OtherSecondSubclass < FirstSubclass end end - # rubocop:disable Rspec/DescribedClass describe 'Reloading enum definition' do - it 'plays nice with lazy loading in Ruby on Rails' do + it 'can be lazy reloaded' do class_body = proc do include Ruby::Enum - define :BALD, 'bald' + define :BUZZ_CUT, 'buzz_cut' end HairStyles = Class.new(&class_body) @@ -256,7 +255,6 @@ class OtherSecondSubclass < FirstSubclass expect { HairStyles.class_eval(&class_body) }.not_to raise_error end end - # rubocop:enable Rspec/DescribedClass describe 'Given a class that has not defined any enums' do class EmptyEnums From 45390ae086fe5c84aa3c72a989e964b271b449a8 Mon Sep 17 00:00:00 2001 From: Nazar Matus Date: Wed, 17 Jun 2026 14:20:07 +0300 Subject: [PATCH 4/4] Add test for subclass lazy reloading --- spec/ruby-enum/enum_spec.rb | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/spec/ruby-enum/enum_spec.rb b/spec/ruby-enum/enum_spec.rb index c650c31..0003700 100644 --- a/spec/ruby-enum/enum_spec.rb +++ b/spec/ruby-enum/enum_spec.rb @@ -250,9 +250,36 @@ class OtherSecondSubclass < FirstSubclass define :BUZZ_CUT, 'buzz_cut' end - HairStyles = Class.new(&class_body) + hair_styles = Class.new(&class_body) - expect { HairStyles.class_eval(&class_body) }.not_to raise_error + expect { hair_styles.class_eval(&class_body) }.not_to raise_error + end + + context 'when a subclass is defined' do + it 'NEEDS to include Ruby::Enum explicitly to be lazy reloaded' do + hair_styles = Class.new do + include Ruby::Enum + + define :BUZZ_CUT, 'buzz_cut' + end + + broken_subclass_body = proc do + define :PONYTAIL, 'ponytail' + end + broken_subclass = Class.new(hair_styles, &broken_subclass_body) + expect { broken_subclass.class_eval(&broken_subclass_body) } + .to raise_error Ruby::Enum::Errors::DuplicateKeyError, /PONYTAIL/ + + subclass_body = proc do + include Ruby::Enum + + define :PONYTAIL, 'ponytail' + end + more_hair_styles = Class.new(hair_styles, &subclass_body) + + expect { more_hair_styles.class_eval(&subclass_body) }.not_to raise_error + expect(more_hair_styles.values).to eq(%w[buzz_cut ponytail]) + end end end