delegate icon indicating copy to clipboard operation
delegate copied to clipboard

Define `DelegateClass` methods in separate module

Open jonathanhefner opened this issue 3 years ago • 3 comments

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.

jonathanhefner avatar Oct 23 '22 03:10 jonathanhefner

https://bugs.ruby-lang.org/issues/19074#note-3

byroot avatar Oct 25 '22 10:10 byroot

Why not prepend Helper?

nobu avatar Nov 22 '22 03:11 nobu

@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.

byroot avatar Nov 22 '22 08:11 byroot