Skip to content

Commit

Permalink
Let the prepend manager control its state
Browse files Browse the repository at this point in the history
  • Loading branch information
waiting-for-dev committed Oct 25, 2023
1 parent 96714d4 commit 7f0530b
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 29 deletions.
18 changes: 4 additions & 14 deletions lib/dry/operation/class_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ module ClassContext
# already been defined in self
# @raise [PrependConfigurationError] if there's already a prepended method
def operate_on(*methods)
@_mutex.synchronize do
@_prepend_manager = @_prepend_manager.register(*methods)
end
@_prepend_manager.register(*methods)
end

# Skips prepending any method
Expand All @@ -31,15 +29,12 @@ def operate_on(*methods)
#
# @raise [PrependConfigurationError] if there's already a prepended method
def skip_prepending
@_mutex.synchronize do
@_prepend_manager = @_prepend_manager.void
end
@_prepend_manager.void
end

# @api private
def inherited(klass)
super
klass.instance_variable_set(:@_mutex, Mutex.new)
if klass.superclass == Dry::Operation
ClassContext.directly_inherited(klass)
else
Expand All @@ -60,10 +55,7 @@ def self.directly_inherited(klass)
def self.indirectly_inherited(klass)
klass.instance_variable_set(
:@_prepend_manager,
klass.superclass.instance_variable_get(:@_prepend_manager).with(
klass: klass,
prepended_methods: []
)
klass.superclass.instance_variable_get(:@_prepend_manager).for_subclass(klass)
)
end

Expand All @@ -72,9 +64,7 @@ module MethodAddedHook
def method_added(method)
super

@_mutex.synchronize do
@_prepend_manager = @_prepend_manager.call(method: method)
end
@_prepend_manager.call(method: method)
end
end
end
Expand Down
26 changes: 11 additions & 15 deletions lib/dry/operation/class_context/prepend_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,29 @@ def register(*methods)
if already_defined_methods.any?
raise MethodsToPrependAlreadyDefinedError.new(methods: already_defined_methods)
else
with(methods_to_prepend: methods)
@methods_to_prepend = methods
end
end

def void
ensure_pristine

with(methods_to_prepend: [])
end

def with(
klass: @klass,
methods_to_prepend: @methods_to_prepend,
prepended_methods: @prepended_methods
)
self.class.new(
klass: klass,
methods_to_prepend: methods_to_prepend,
prepended_methods: prepended_methods
)
@methods_to_prepend = []
end

def call(method:)
return self unless @methods_to_prepend.include?(method)

@klass.include(MethodPrepender.new(method: method))
with(prepended_methods: @prepended_methods + [method])
@prepended_methods += [method]
end

def for_subclass(subclass)
self.class.new(
klass: subclass,
methods_to_prepend: @methods_to_prepend.dup,
prepended_methods: []
)
end

private
Expand Down
34 changes: 34 additions & 0 deletions spec/integration/operations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,23 @@ def call; end
end
}.not_to raise_error
end

it "doesn't leak from subclasses to other classes in the inheritance tree" do
klass = Class.new(Dry::Operation) do
def add_one(x) = Success(x + 1)
end
Class.new(klass) do
operate_on :run
end

klass.define_method(:run) do |x|
step add_one(x)
end

expect(
klass.new.run(1)
).to eq(2)
end
end

context ".skip_prepending" do
Expand Down Expand Up @@ -237,5 +254,22 @@ def call; end
end
}.not_to raise_error
end

it "doesn't leak from subclasses to other classes in the inheritance tree" do
klass = Class.new(Dry::Operation) do
def add_one(x) = Success(x + 1)
end
Class.new(klass) do
skip_prepending
end

klass.define_method(:call) do |x|
step add_one(x)
end

expect(
klass.new.(1)
).to eq(Success(2))
end
end
end

0 comments on commit 7f0530b

Please sign in to comment.