diff --git a/CHANGELOG.md b/CHANGELOG.md index f1350f4..100a5a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ _No breaking changes!_ **Project enhancements:** - Fixed a bug that overwrote existing self.extended method definitions. [[#324]](https://github.com/panorama-ed/memo_wise/pull/314) +- Fixed a bug that overwrote existing self.inherited method definitions. [[#325]](https://github.com/panorama-ed/memo_wise/pull/315) ## [v1.8.0](https://github.com/panorama-ed/memo_wise/compare/v1.7.0...v1.8.0) - 2023-10-25 diff --git a/lib/memo_wise.rb b/lib/memo_wise.rb index 6c9c67b..f88ff80 100644 --- a/lib/memo_wise.rb +++ b/lib/memo_wise.rb @@ -86,6 +86,14 @@ def extended(base) end private_constant(:CreateMemoWiseStateOnExtended) + module CreateMemoWiseStateOnInherited + def inherited(subclass) + super + MemoWise::InternalAPI.create_memo_wise_state!(subclass) + end + end + private_constant(:CreateMemoWiseStateOnInherited) + # @private # # Private setup method, called automatically by `prepend MemoWise` in a class. @@ -165,12 +173,15 @@ def memo_wise(method_name_or_hash) # This ensures that a memoized method defined on a parent class can # still be used in a child class. - klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 - def inherited(subclass) - super - MemoWise::InternalAPI.create_memo_wise_state!(subclass) + if klass.is_a?(Class) + if klass.singleton_class? + klass.prepend(CreateMemoWiseStateOnInherited) + else + klass.singleton_class.prepend(CreateMemoWiseStateOnInherited) end - HEREDOC + else + klass.prepend(CreateMemoWiseStateOnInherited) + end raise ArgumentError, "#{method_name.inspect} must be a Symbol" unless method_name.is_a?(Symbol) diff --git a/spec/memo_wise_spec.rb b/spec/memo_wise_spec.rb index 7278ac0..b72321d 100644 --- a/spec/memo_wise_spec.rb +++ b/spec/memo_wise_spec.rb @@ -842,5 +842,85 @@ def no_args; end expect(instance.instance_variable_get(:@extended_called)).to be(true) end end + + context "with target defined self.inherited" do + context "when target is class" do + let(:klass) do + Class.new do + prepend MemoWise + + def self.inherited(subclass) + subclass.instance_variable_set(:@inherited_called, true) + end + + def no_args; end + memo_wise :no_args + end + end + + it "calls defined self.inherited" do + expect(Class.new(klass).instance_variable_get(:@inherited_called)).to be(true) + end + + it "doesn't define #inherited" do + expect(klass).to be_respond_to(:inherited) + expect(klass.new).to_not be_respond_to(:inherited) + end + end + + context "when target is singleton class" do + let(:klass) do + Class.new do + class << self + prepend MemoWise + + def inherited(subclass) + subclass.instance_variable_set(:@inherited_called, true) + end + + def no_args; end + memo_wise :no_args + end + end + end + + it "calls defined self.inherited" do + expect(Class.new(klass).instance_variable_get(:@inherited_called)).to be(true) + end + + it "doesn't define #inherited" do + expect(klass).to be_respond_to(:inherited) + expect(klass.new).to_not be_respond_to(:inherited) + end + end + + context "when target is module" do + let(:klass) do + mod = Module.new do + prepend MemoWise + + def inherited(subclass) + subclass.instance_variable_set(:@inherited_called, true) + end + + def no_args; end + memo_wise :no_args + end + + klass = Class.new + klass.extend(mod) + klass + end + + it "calls defined self.inherited" do + expect(Class.new(klass).instance_variable_get(:@inherited_called)).to be(true) + end + + it "doesn't define #inherited" do + expect(klass).to be_respond_to(:inherited) + expect(klass.new).to_not be_respond_to(:inherited) + end + end + end end end