From 0cb7994ee15abba6c114fd2b8c58e39673f36b66 Mon Sep 17 00:00:00 2001 From: Jonathan Hefner Date: Sat, 22 Oct 2022 17:25:32 -0500 Subject: [PATCH] Define DelegateClass methods in separate module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, modules included in a `DelegateClass` could not override delegate methods: ```ruby 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: ```ruby 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** ```ruby # 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. --- lib/delegate.rb | 6 ++++-- test/test_delegate.rb | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/delegate.rb b/lib/delegate.rb index af95c86..572a1c7 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -410,16 +410,18 @@ def __setobj__(obj) # :nodoc: __raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj) @delegate_dc_obj = obj end + end + methods_module = Module.new do protected_instance_methods.each do |method| define_method(method, Delegator.delegating_block(method)) - alias_method(method, method) protected method end public_instance_methods.each do |method| define_method(method, Delegator.delegating_block(method)) - alias_method(method, method) end end + klass.const_set("DelegateClass_Methods", methods_module) + klass.include(methods_module) klass.define_singleton_method :public_instance_methods do |all=true| super(all) | superclass.public_instance_methods end diff --git a/test/test_delegate.rb b/test/test_delegate.rb index 431d134..0286b17 100644 --- a/test/test_delegate.rb +++ b/test/test_delegate.rb @@ -41,6 +41,16 @@ def first assert_empty(warning) end + def test_delegate_class_block_with_override_from_module + mod = Module.new do + def first + super.inspect + end + end + klass = DelegateClass(Array) { include mod } + assert_equal("1", klass.new([1]).first) + end + def test_systemcallerror_eq e = SystemCallError.new(0) assert((SimpleDelegator.new(e) == e) == (e == SimpleDelegator.new(e)), "[ruby-dev:34808]")