Introduce Module#delegate_missing_to - #23930
Conversation
When building decorators, a common pattern may emerge:
class Partition
def initialize(first_event)
@events = [ first_event ]
end
def people
if @events.first.detail.people.any?
@events.collect { |e| Array(e.detail.people) }.flatten.uniq
else
@events.collect(&:creator).uniq
end
end
private
def respond_to_missing?(name, include_private = false)
@events.respond_to?(name, include_private)
end
def method_missing(method, *args, &block)
@events.send(method, *args, &block)
end
end
With `Module#delegate_missing_to`, the above is condensed to:
class Partition
delegate_missing_to :@events
def initialize(first_event)
@events = [ first_event ]
end
def people
if @events.first.detail.people.any?
@events.collect { |e| Array(e.detail.people) }.flatten.uniq
else
@events.collect(&:creator).uniq
end
end
end
David suggested it in rails#23824.
|
r? @schneems (@rails-bot has picked a reviewer for you, use r? to override) |
|
r? @dhh |
| end | ||
|
|
||
| def method_missing(method, *args, &block) | ||
| #{target}.send(method, *args, &block) |
There was a problem hiding this comment.
Is it worth considering using public_send instead of send here?
There was a problem hiding this comment.
I don't think so. The delegation should be fully. Even private methods should be delegated.
There was a problem hiding this comment.
@rafaelfranca that has some pretty questionable results -- e.g., external calls to the private Kernel methods will now work on this object. I guess you want local methods to be able to call private instance methods... but IMO, it seems fair to force those to be more explicit: we're composing around the object's public API, not injecting methods inside it. Arguably, it'd be weirder if we could call private methods, yet have a separate set of ivars.
There was a problem hiding this comment.
oh, yeah, that is what I wanted. I agree we should use public_send here.
There was a problem hiding this comment.
Comparing with stdlib Delegator, it sends to the target if it responds to the method (effectively a publis_send) and supers if not (so base method_missing deals with an unimplemented method rather than raising NoMethodError on our public_send).
There was a problem hiding this comment.
Seeing 3ac9956 switches to a respond_to? + public_send and supers.
Introduce Module#delegate_missing_to
| # end | ||
| # | ||
| # The target can be anything callable withing the object. E.g. instance | ||
| # variables, methods, constants ant the likes. |
There was a problem hiding this comment.
This explains what it does, but isn't enough guidance to decide whether to use delegate …, to: @events (explicitly delegate specific methods to a composed object), delegate_missing_to :@events (blanket-delegate public methods to a composed object, but don't act as a transparent proxy), or Ruby stdlib SimpleDelegator (full-class delegation to act as a transparent proxy) or Forwardable (single-method delegation like delegate …, to: …).
There was a problem hiding this comment.
👍 Will get on it later on tonight, unless someone beats me to it.
And make sure that it doesn't even try to call the method in the target.
When building decorators, a common pattern may emerge:
With
Module#delegate_missing_to, the above is condensed to:David suggested it in #23824.