delegate
delegate copied to clipboard
Define `DelegateClass` methods in separate module
Before this commit, modules included in a DelegateClass could not override delegate methods:
Base = Class.new do
def foo
"base"
end
end
Helper = Module.new do
def foo
"helper"
end
end
WithHelper = DelegateClass(Base) { include Helper }
WithHelper.new(Base.new).foo
# => "base"
This commit defines delegate methods in a separate module, so other modules can come before it in the method lookup chain:
WithHelper.new(Base.new).foo
# => "helper"
Also, because of this change, methods in a DelegateClass block will properly override instead of redefine. Therefore, calling super is faster:
Benchmark script
# frozen_string_literal: true
require "benchmark/ips"
$LOAD_PATH.prepend(".../delegate/lib")
require "delegate"
Base = Class.new do
def foo
end
end
Overridden = DelegateClass(Base) do
def foo
super
end
end
overridden = Overridden.new(Base.new)
Benchmark.ips do |x|
x.report("super") { overridden.foo }
end
Before
Warming up --------------------------------------
super 75.044k i/100ms
Calculating -------------------------------------
super 759.506k (± 0.8%) i/s - 3.827M in 5.039488s
After
Warming up --------------------------------------
super 184.164k i/100ms
Calculating -------------------------------------
super 1.835M (± 1.0%) i/s - 9.208M in 5.019711s
Fixes https://bugs.ruby-lang.org/issues/19079.
https://bugs.ruby-lang.org/issues/19074#note-3
Why not prepend Helper?
@nobu sure you can do that, but you'd have a similar-ish issue with just defining a method in the delegator:
Foo = Struct.new(:field)
FooDelegator = DelegateClass(Foo) do
def field
super.to_s
end
end
In this example super doesn't call the method that DelegateClass generated, but fallback to Delegator#method_missing, it works but kinda defeat the purpose.
It's somewhat assumed that if you create a delegator, you will want to specialize a few methods.