Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 21 additions & 17 deletions lib/delegate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -344,13 +344,6 @@ def __setobj__(obj)
end
end

def Delegator.delegating_block(mid) # :nodoc:
lambda do |*args, &block|
target = self.__getobj__
target.__send__(mid, *args, &block)
end.ruby2_keywords
end

#
# The primary interface to this library. Use to setup delegation when defining
# your class.
Expand Down Expand Up @@ -400,14 +393,29 @@ def DelegateClass(superclass, &block)
public_instance_methods = superclass.public_instance_methods
public_instance_methods -= ignores

normal, special = public_instance_methods.partition { |m| m.match?(/\A[a-zA-Z]\w*[!\?]?\z/) }
methods_to_define =
public_instance_methods.map { |x| [x, false] } +
protected_instance_methods.map { |x| [x, true] }

source = normal.map do |method|
"def #{method}(...); __getobj__.#{method}(...); end"
end
source = []

protected_instance_methods.each do |method|
source << "def #{method}(...); __getobj__.__send__(#{method.inspect}, ...); end"
methods_to_define.each do |target_name, is_protected|
unless target_name.match?(/\A[_a-zA-Z]\w*[!\?]?\z/)
placeholder_name = :__delegate
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
placeholder_name = :__delegate
placeholder_name = "__delegate_#{target_name.unpack1("h*")}"

Active Model unpack the string in hexadecimal to get a unique name. May or may not be a good idea. Make sure there is no conflict and get unique name, but also define more symbols?

Up to you.

Copy link
Member Author

@jhawthorn jhawthorn Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to avoid creating extra symbols and since we're removing them I don't think there's a need for them to be unique.

It might be worth handling the case that we're delegating to something which has exactly __delegate defined...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense.

end

send_source =
if is_protected || placeholder_name
"__getobj__.__send__(#{target_name.inspect}, ...)"
else
"__getobj__.#{target_name}(...)"
end
source << "def #{placeholder_name || target_name}(...); #{send_source}; end"

if placeholder_name
source << "alias_method #{target_name.inspect}, :#{placeholder_name}"
source << "remove_method :#{placeholder_name}"
end
end

klass.module_eval do
Expand All @@ -426,10 +434,6 @@ def __setobj__(obj) # :nodoc:

class_eval(source.join(";"), __FILE__, __LINE__)

special.each do |method|
define_method(method, Delegator.delegating_block(method))
end

protected(*protected_instance_methods)
end

Expand Down
16 changes: 16 additions & 0 deletions test/test_delegate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,22 @@ def test_call_visibiltiy
assert_raise(NoMethodError) { obj.__send__(:parent_private) }
end

class ClassWithInvalidName
define_method(:" ") { :space }
define_method(:"\t") { :tab }
protected :"\t"
end

def test_delegateclass_invalid_name
delegate = DelegateClass(ClassWithInvalidName)
instance = delegate.new(ClassWithInvalidName.new)
assert_equal :space, instance.send(:" ")
assert_equal :space, instance.__send__(:" ")

assert_equal :tab, instance.send(:"\t")
assert_equal :tab, instance.__send__(:"\t")
end

class IV < DelegateClass(Integer)
attr_accessor :var

Expand Down