From: elliot.winkler@... Date: 2015-12-28T23:56:22+00:00 Subject: [ruby-core:72579] [Ruby trunk - Bug #11916] [Open] Fix delegating to 'args' and 'block' Issue #11916 has been reported by Elliot Winkler. ---------------------------------------- Bug #11916: Fix delegating to 'args' and 'block' https://bugs.ruby-lang.org/issues/11916 * Author: Elliot Winkler * Status: Open * Priority: Normal * Assignee: * ruby -v: 2.4.0dev * Backport: 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN ---------------------------------------- If you have a class that uses Forwardable to delegate a method to another object, and the method that returns the delegate object is called `args` or `block`, then Forwardable will fail to work. Here's a simple example: class ModelCreator extend Forwardable attr_reader :args def_delegator :args, :model_name def initialize(args) @args = args end end ModelCreator.new.model_name If you run the last line above, then you'll get: NoMethodError: undefined method `model_name' for []:Array This error occurs because `def_delegator` -- as it is written in Ruby -- uses metaprogramming to add methods to the class that will then delegate to the delegate object. So it's as if we had written: class ModelCreator extend Forwardable attr_reader :args def model_name(*args, &block) args.model_name(*args, &block) end def initialize(args) @args = args end end As you can see, `def_delegator` will not only forward the method call onto the delegate object, it will also forward any arguments provided as well. It is here that the bug arises: it splats all of the arguments into a variable which is called `args`, and because of how variable scope works in Ruby, it then attempts to call `model_name` on *this* variable and *not* our delegate object method. The fix is to call the delegate object method manually using `__send__`. (This assumes, of course, that the given receiver is, in fact, the name of a method and not the name of an instance variable, which is also a possibility.) We use `__send__` because the delegate object method could be private. So, that looks like this: def model_name(*args, &block) __send__(:args).model_name(*args, &block) end Because `def_delegators` and `delegate` use `def_delegator` internally, they also get this fix as well. ---Files-------------------------------- fix-def-delegator.patch (6.99 KB) -- https://bugs.ruby-lang.org/ Unsubscribe: