1# 2# forwardable.rb - 3# $Release Version: 1.1$ 4# $Revision: 38689 $ 5# by Keiju ISHITSUKA(keiju@ishitsuka.com) 6# original definition by delegator.rb 7# Revised by Daniel J. Berger with suggestions from Florian Gross. 8# 9# Documentation by James Edward Gray II and Gavin Sinclair 10 11 12 13# The Forwardable module provides delegation of specified 14# methods to a designated object, using the methods #def_delegator 15# and #def_delegators. 16# 17# For example, say you have a class RecordCollection which 18# contains an array <tt>@records</tt>. You could provide the lookup method 19# #record_number(), which simply calls #[] on the <tt>@records</tt> 20# array, like this: 21# 22# class RecordCollection 23# extend Forwardable 24# def_delegator :@records, :[], :record_number 25# end 26# 27# Further, if you wish to provide the methods #size, #<<, and #map, 28# all of which delegate to @records, this is how you can do it: 29# 30# class RecordCollection 31# # extend Forwardable, but we did that above 32# def_delegators :@records, :size, :<<, :map 33# end 34# f = Foo.new 35# f.printf ... 36# f.gets 37# f.content_at(1) 38# 39# If the object isn't a Module and Class, You can too extend Forwardable 40# module. 41# 42# printer = String.new 43# printer.extend Forwardable # prepare object for delegation 44# printer.def_delegator "STDOUT", "puts" # add delegation for STDOUT.puts() 45# printer.puts "Howdy!" 46# 47# == Another example 48# 49# We want to rely on what has come before obviously, but with delegation we can 50# take just the methods we need and even rename them as appropriate. In many 51# cases this is preferable to inheritance, which gives us the entire old 52# interface, even if much of it isn't needed. 53# 54# class Queue 55# extend Forwardable 56# 57# def initialize 58# @q = [ ] # prepare delegate object 59# end 60# 61# # setup preferred interface, enq() and deq()... 62# def_delegator :@q, :push, :enq 63# def_delegator :@q, :shift, :deq 64# 65# # support some general Array methods that fit Queues well 66# def_delegators :@q, :clear, :first, :push, :shift, :size 67# end 68# 69# q = Queue.new 70# q.enq 1, 2, 3, 4, 5 71# q.push 6 72# 73# q.shift # => 1 74# while q.size > 0 75# puts q.deq 76# end 77# 78# q.enq "Ruby", "Perl", "Python" 79# puts q.first 80# q.clear 81# puts q.first 82# 83# This should output: 84# 85# 2 86# 3 87# 4 88# 5 89# 6 90# Ruby 91# nil 92# 93# == Notes 94# 95# Be advised, RDoc will not detect delegated methods. 96# 97# +forwardable.rb+ provides single-method delegation via the def_delegator and 98# def_delegators methods. For full-class delegation via DelegateClass, see 99# +delegate.rb+. 100# 101module Forwardable 102 # Version of +forwardable.rb+ 103 FORWARDABLE_VERSION = "1.1.0" 104 105 @debug = nil 106 class << self 107 # If true, <tt>__FILE__</tt> will remain in the backtrace in the event an 108 # Exception is raised. 109 attr_accessor :debug 110 end 111 112 # Takes a hash as its argument. The key is a symbol or an array of 113 # symbols. These symbols correspond to method names. The value is 114 # the accessor to which the methods will be delegated. 115 # 116 # :call-seq: 117 # delegate method => accessor 118 # delegate [method, method, ...] => accessor 119 # 120 def instance_delegate(hash) 121 hash.each{ |methods, accessor| 122 methods = [methods] unless methods.respond_to?(:each) 123 methods.each{ |method| 124 def_instance_delegator(accessor, method) 125 } 126 } 127 end 128 129 # 130 # Shortcut for defining multiple delegator methods, but with no 131 # provision for using a different name. The following two code 132 # samples have the same effect: 133 # 134 # def_delegators :@records, :size, :<<, :map 135 # 136 # def_delegator :@records, :size 137 # def_delegator :@records, :<< 138 # def_delegator :@records, :map 139 # 140 def def_instance_delegators(accessor, *methods) 141 methods.delete("__send__") 142 methods.delete("__id__") 143 for method in methods 144 def_instance_delegator(accessor, method) 145 end 146 end 147 148 # Define +method+ as delegator instance method with an optional 149 # alias name +ali+. Method calls to +ali+ will be delegated to 150 # +accessor.method+. 151 # 152 # class MyQueue 153 # extend Forwardable 154 # attr_reader :queue 155 # def initialize 156 # @queue = [] 157 # end 158 # 159 # def_delegator :@queue, :push, :mypush 160 # end 161 # 162 # q = MyQueue.new 163 # q.mypush 42 164 # q.queue #=> [42] 165 # q.push 23 #=> NoMethodError 166 # 167 def def_instance_delegator(accessor, method, ali = method) 168 line_no = __LINE__; str = %{ 169 def #{ali}(*args, &block) 170 begin 171 #{accessor}.__send__(:#{method}, *args, &block) 172 rescue Exception 173 $@.delete_if{|s| %r"#{Regexp.quote(__FILE__)}"o =~ s} unless Forwardable::debug 174 ::Kernel::raise 175 end 176 end 177 } 178 # If it's not a class or module, it's an instance 179 begin 180 module_eval(str, __FILE__, line_no) 181 rescue 182 instance_eval(str, __FILE__, line_no) 183 end 184 185 end 186 187 alias delegate instance_delegate 188 alias def_delegators def_instance_delegators 189 alias def_delegator def_instance_delegator 190end 191 192# SingleForwardable can be used to setup delegation at the object level as well. 193# 194# printer = String.new 195# printer.extend SingleForwardable # prepare object for delegation 196# printer.def_delegator "STDOUT", "puts" # add delegation for STDOUT.puts() 197# printer.puts "Howdy!" 198# 199# Also, SingleForwardable can be used to set up delegation for a Class or Module. 200# 201# class Implementation 202# def self.service 203# puts "serviced!" 204# end 205# end 206# 207# module Facade 208# extend SingleForwardable 209# def_delegator :Implementation, :service 210# end 211# 212# Facade.service #=> serviced! 213# 214# If you want to use both Forwardable and SingleForwardable, you can 215# use methods def_instance_delegator and def_single_delegator, etc. 216module SingleForwardable 217 # Takes a hash as its argument. The key is a symbol or an array of 218 # symbols. These symbols correspond to method names. The value is 219 # the accessor to which the methods will be delegated. 220 # 221 # :call-seq: 222 # delegate method => accessor 223 # delegate [method, method, ...] => accessor 224 # 225 def single_delegate(hash) 226 hash.each{ |methods, accessor| 227 methods = [methods] unless methods.respond_to?(:each) 228 methods.each{ |method| 229 def_single_delegator(accessor, method) 230 } 231 } 232 end 233 234 # 235 # Shortcut for defining multiple delegator methods, but with no 236 # provision for using a different name. The following two code 237 # samples have the same effect: 238 # 239 # def_delegators :@records, :size, :<<, :map 240 # 241 # def_delegator :@records, :size 242 # def_delegator :@records, :<< 243 # def_delegator :@records, :map 244 # 245 def def_single_delegators(accessor, *methods) 246 methods.delete("__send__") 247 methods.delete("__id__") 248 for method in methods 249 def_single_delegator(accessor, method) 250 end 251 end 252 253 # :call-seq: 254 # def_single_delegator(accessor, method, new_name=method) 255 # 256 # Defines a method _method_ which delegates to _accessor_ (i.e. it calls 257 # the method of the same name in _accessor_). If _new_name_ is 258 # provided, it is used as the name for the delegate method. 259 def def_single_delegator(accessor, method, ali = method) 260 str = %{ 261 def #{ali}(*args, &block) 262 begin 263 #{accessor}.__send__(:#{method}, *args, &block) 264 rescue Exception 265 $@.delete_if{|s| %r"#{Regexp.quote(__FILE__)}"o =~ s} unless Forwardable::debug 266 ::Kernel::raise 267 end 268 end 269 } 270 271 instance_eval(str, __FILE__, __LINE__) 272 end 273 274 alias delegate single_delegate 275 alias def_delegators def_single_delegators 276 alias def_delegator def_single_delegator 277end 278