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
-
FORWARDABLE_VERSION =
# File 'lib/forwardable/version.rb', line 4VERSION
-
VERSION =
Version of
forwardable.rb
"1.3.1"
Class Attribute Summary
-
.debug
rw
ignored.
Class Method Summary
- ._compile_method(src, file, line) Internal use only
- ._delegator_method(obj, accessor, method, ali) Internal use only
- ._valid_method?(method) ⇒ Boolean Internal use only
Instance Method Summary
-
#def_delegator(accessor, method, ali = method)
Alias for #def_instance_delegator.
-
#def_delegators(accessor, *methods)
Alias for #def_instance_delegators.
-
#def_instance_delegator(accessor, method, ali = method)
(also: #def_delegator)
Define
method
as delegator instance method with an optional alias nameali
. -
#def_instance_delegators(accessor, *methods)
(also: #def_delegators)
Shortcut for defining multiple delegator methods, but with no provision for using a different name.
-
#delegate(hash)
Alias for #instance_delegate.
-
#delegate(method) ⇒ accessor
(also: #delegate)
Takes a hash as its argument.
Class Attribute Details
.debug (rw)
ignored
# File 'lib/forwardable.rb', line 118
attr_accessor :debug
Class Method Details
._compile_method(src, file, line)
# 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)
# 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
# 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)
Alias for #def_instance_delegator.
# File 'lib/forwardable.rb', line 195
alias def_delegator def_instance_delegator
#def_delegators(accessor, *methods)
Alias for #def_instance_delegators.
# 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
# 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
# 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.
# File 'lib/forwardable.rb', line 193
alias delegate instance_delegate
#delegate(method) ⇒ accessor
#delegate([method, method, ...]=> accessor)
Also known as: #delegate
accessor
#delegate([method, method, ...]=> accessor)
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.
# 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