123456789_123456789_123456789_123456789_123456789_

Module: Forwardable

Relationships & Source Files
Defined in: lib/forwardable.rb,
lib/forwardable/impl.rb,
lib/forwardable/version.rb

Overview

The Forwardable module provides delegation of specified methods to a designated object, using the methods #def_delegator and #def_delegators.

For example, say you have a class RecordCollection which contains an array @records. You could provide the lookup method #record_number(), which simply calls #[] on the @records array, like this:

require 'forwardable'

class RecordCollection
  attr_accessor :records
  extend Forwardable
  def_delegator :@records, :[], :record_number
end

We can use the lookup method like so:

r = RecordCollection.new
r.records = [4,5,6]
r.record_number(0)  # => 4

Further, if you wish to provide the methods #size, #<<, and #map, all of which delegate to @records, this is how you can do it:

class RecordCollection # re-open RecordCollection class
  def_delegators :@records, :size, :<<, :map
end

r = RecordCollection.new
r.records = [1,2,3]
r.record_number(0)   # => 1
r.size               # => 3
r << 4               # => [1, 2, 3, 4]
r.map { |x| x * 2 }  # => [2, 4, 6, 8]

You can even extend regular objects with Forwardable.

my_hash = Hash.new
my_hash.extend Forwardable              # prepare object for delegation
my_hash.def_delegator "STDOUT", "puts"  # add delegation for STDOUT.puts()
my_hash.puts "Howdy!"

Another example

You could use Forwardable as an alternative to inheritance, when you don’t want to inherit all methods from the superclass. For instance, here is how you might add a range of Array instance methods to a new class Queue:

class Queue
  extend Forwardable

  def initialize
    @q = [ ]    # prepare delegate object
  end

  # setup preferred interface, enq() and deq()...
  def_delegator :@q, :push, :enq
  def_delegator :@q, :shift, :deq

  # support some general Array methods that fit Queues well
  def_delegators :@q, :clear, :first, :push, :shift, :size
end

q = Queue.new
q.enq 1, 2, 3, 4, 5
q.push 6

q.shift    # => 1
while q.size > 0
  puts q.deq
end

q.enq "Ruby", "Perl", "Python"
puts q.first
q.clear
puts q.first

This should output:

2
3
4
5
6
Ruby
nil

Notes

Be advised, RDoc will not detect delegated methods.

forwardable.rb provides single-method delegation via the def_delegator and def_delegators methods. For full-class delegation via DelegateClass, see delegate.rb.

Constant Summary

Class Attribute Summary

Class Method Summary

Instance Method Summary

Class Attribute Details

.debug (rw)

ignored

[ GitHub ]

  
# File 'lib/forwardable.rb', line 118

attr_accessor :debug

Class Method Details

._compile_method(src, file, line)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/forwardable/impl.rb', line 13

def self._compile_method(src, file, line)
  eval(src, nil, file, line)
end

._delegator_method(obj, accessor, method, ali)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/forwardable.rb', line 198

def self._delegator_method(obj, accessor, method, ali)
  accessor = accessor.to_s unless Symbol === accessor

  if Module === obj ?
       obj.method_defined?(accessor) || obj.private_method_defined?(accessor) :
       obj.respond_to?(accessor, true)
    accessor = "#{accessor}()"
  end

  method_call = ".__send__(:#{method}, *args, &block)"
  if _valid_method?(method)
    loc, = caller_locations(2,1)
    pre = "_ ="
    mesg = "#{Module === obj ? obj : obj.class}\##{ali} at #{loc.path}:#{loc.lineno} forwarding to private method "
    method_call = "#{<<-"begin;"}\n#{<<-"end;".chomp}"
      begin;
        unless defined? _.#{method}
          ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1
          _#{method_call}
        else
          _.#{method}(*args, &block)
        end
      end;
  end

  _compile_method("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1)
  begin;
    proc do
      def #{ali}(*args, &block)
        #{pre}
        begin
          #{accessor}
        end#{method_call}
      end
    end
  end;
end

._valid_method?(method) ⇒ Boolean

This method is for internal use only.
[ GitHub ]

  
# File 'lib/forwardable/impl.rb', line 3

def self._valid_method?(method)
  catch {|tag|
    eval("BEGIN{throw tag}; ().#{method}", binding, __FILE__, __LINE__)
  }
rescue SyntaxError
  false
else
  true
end

Instance Method Details

#def_delegator(accessor, method, ali = method)

[ GitHub ]

  
# File 'lib/forwardable.rb', line 195

alias def_delegator def_instance_delegator

#def_delegators(accessor, *methods)

[ GitHub ]

  
# File 'lib/forwardable.rb', line 194

alias def_delegators def_instance_delegators

#def_instance_delegator(accessor, method, ali = method) Also known as: #def_delegator

Define method as delegator instance method with an optional alias name ali. Method calls to ali will be delegated to accessor.method. accessor should be a method name, instance variable name, or constant name. Use the full path to the constant if providing the constant name. Returns the name of the method defined.

class MyQueue
  CONST = 1
  extend Forwardable
  attr_reader :queue
  def initialize
    @queue = []
  end

  def_delegator :@queue, :push, :mypush
  def_delegator 'MyQueue::CONST', :to_i
end

q = MyQueue.new
q.mypush 42
q.queue    #=> [42]
q.push 23  #=> NoMethodError
q.to_i     #=> 1
[ GitHub ]

  
# File 'lib/forwardable.rb', line 183

def def_instance_delegator(accessor, method, ali = method)
  gen = Forwardable._delegator_method(self, accessor, method, ali)

  # If it's not a class or module, it's an instance
  mod = Module === self ? self : singleton_class
  ret = mod.module_eval(&gen)
  mod.send(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7'
  ret
end

#def_instance_delegators(accessor, *methods) Also known as: #def_delegators

Shortcut for defining multiple delegator methods, but with no provision for using a different name. The following two code samples have the same effect:

def_delegators :@records, :size, :<<, :map

def_delegator :@records, :size
def_delegator :@records, :<<
def_delegator :@records, :map
[ GitHub ]

  
# File 'lib/forwardable.rb', line 151

def def_instance_delegators(accessor, *methods)
  methods.each do |method|
    next if /\A__(?:send|id)__\z/ =~ method
    def_instance_delegator(accessor, method)
  end
end

#delegate(hash)

Alias for #instance_delegate.

[ GitHub ]

  
# File 'lib/forwardable.rb', line 193

alias delegate instance_delegate

#delegate(method) ⇒ accessor #delegate([method, method, ...]=> accessor)
Also known as: #delegate

Takes a hash as its argument. The key is a symbol or an array of symbols. These symbols correspond to method names, instance variable names, or constant names (see def_delegator). The value is the accessor to which the methods will be delegated.

[ GitHub ]

  
# File 'lib/forwardable.rb', line 130

def instance_delegate(hash)
  hash.each do |methods, accessor|
    unless defined?(methods.each)
      def_instance_delegator(accessor, methods)
    else
      methods.each {|method| def_instance_delegator(accessor, method)}
    end
  end
end