Skip to content

Commit 1df286b

Browse files
Allow memoization when included modules prepend MemoWise and define initialize
Fixes #302 Co-authored-by: alpaca-tc <alpaca-tc@alpaca.tc>
1 parent 13f7ae7 commit 1df286b

File tree

3 files changed

+66
-7
lines changed

3 files changed

+66
-7
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
99

1010
**Gem enhancements:** none
1111

12-
_No breaking changes!_
12+
- Allow memoization when `include`d modules `prepend MemoWise` and define `initialize` [[#327]](https://github.com/panorama-ed/memo_wise/pull/327)
1313

1414
**Project enhancements:** none
1515

lib/memo_wise.rb

+18-6
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,9 @@ module MemoWise
5353
# [this article](https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/)
5454
# for more information.
5555
#
56+
5657
# :nocov:
57-
all_args = RUBY_VERSION < "2.7" ? "*" : "..."
58-
# :nocov:
59-
class_eval <<~HEREDOC, __FILE__, __LINE__ + 1
58+
INITIALIZE_LITERAL = <<~HEREDOC.freeze
6059
# On Ruby 2.7 or greater:
6160
#
6261
# def initialize(...)
@@ -71,11 +70,14 @@ module MemoWise
7170
# super
7271
# end
7372
74-
def initialize(#{all_args})
73+
def initialize(#{RUBY_VERSION < '2.7' ? '*' : '...'})
7574
MemoWise::InternalAPI.create_memo_wise_state!(self)
7675
super
7776
end
7877
HEREDOC
78+
# :nocov:
79+
80+
class_eval(INITIALIZE_LITERAL, __FILE__, __LINE__ + 1)
7981

8082
module CreateMemoWiseStateOnExtended
8183
def extended(base)
@@ -93,6 +95,15 @@ def inherited(subclass)
9395
end
9496
private_constant(:CreateMemoWiseStateOnInherited)
9597

98+
module CreateMemoWiseStateOnIncluded
99+
def included(base)
100+
base.prepend(Module.new do
101+
class_eval(INITIALIZE_LITERAL, __FILE__, __LINE__ + 1)
102+
end)
103+
end
104+
end
105+
private_constant(:CreateMemoWiseStateOnIncluded)
106+
96107
# @private
97108
#
98109
# Private setup method, called automatically by `prepend MemoWise` in a class.
@@ -155,8 +166,7 @@ def memo_wise(method_name_or_hash)
155166
end
156167
when Hash
157168
unless method_name_or_hash.keys == [:self]
158-
raise ArgumentError,
159-
"`:self` is the only key allowed in memo_wise"
169+
raise ArgumentError, "`:self` is the only key allowed in memo_wise"
160170
end
161171

162172
method_name = method_name_or_hash[:self]
@@ -175,6 +185,8 @@ def memo_wise(method_name_or_hash)
175185
if klass.is_a?(Class) && !klass.singleton_class?
176186
klass.singleton_class.prepend(CreateMemoWiseStateOnInherited)
177187
else
188+
klass.singleton_class.prepend(CreateMemoWiseStateOnIncluded) if klass.is_a?(Module) && !klass.singleton_class?
189+
178190
klass.prepend(CreateMemoWiseStateOnInherited)
179191
end
180192

spec/memo_wise_spec.rb

+47
Original file line numberDiff line numberDiff line change
@@ -351,11 +351,32 @@ def module2_method
351351
end
352352
end
353353

354+
let(:klass_with_initializer) do
355+
Class.new do
356+
include Module1
357+
def initialize(*); end
358+
end
359+
end
360+
361+
let(:module_with_initializer) do
362+
Module.new do
363+
include Module1
364+
def initialize(*); end
365+
end
366+
end
367+
368+
let(:klass_with_module_with_initializer) do
369+
Class.new do
370+
include Module3
371+
end
372+
end
373+
354374
let(:instance) { klass.new }
355375

356376
before(:each) do
357377
stub_const("Module1", module1)
358378
stub_const("Module2", module2)
379+
stub_const("Module3", module_with_initializer)
359380
end
360381

361382
it "memoizes inherited methods separately" do
@@ -364,6 +385,32 @@ def module2_method
364385
expect(Array.new(4) { instance.module2_method }).to all eq("module2_method")
365386
expect(instance.module2_method_counter).to eq(1)
366387
end
388+
389+
it "can memoize klass with initializer" do
390+
instance = klass_with_initializer.new(true)
391+
expect { instance.module1_method }.not_to raise_error
392+
393+
expect(Array.new(4) { instance.module1_method }).to all eq("module1_method")
394+
expect(instance.module1_method_counter).to eq(1)
395+
end
396+
397+
it "can memoize klass with module with initializer" do
398+
instance = klass_with_module_with_initializer.new(true)
399+
expect { instance.module1_method }.not_to raise_error
400+
401+
expect(Array.new(4) { instance.module1_method }).to all eq("module1_method")
402+
expect(instance.module1_method_counter).to eq(1)
403+
end
404+
405+
it "can reset klass with initializer" do
406+
instance = klass_with_initializer.new(true)
407+
expect { instance.reset_memo_wise }.not_to raise_error
408+
end
409+
410+
it "can reset klass with module with initializer" do
411+
instance = klass_with_module_with_initializer.new(true)
412+
expect { instance.reset_memo_wise }.not_to raise_error
413+
end
367414
end
368415

369416
context "when the class, its superclass, and its module all memoize methods" do

0 commit comments

Comments
 (0)