├── .document ├── bin ├── setup └── console ├── .github ├── release.yml ├── dependabot.yml └── workflows │ ├── test.yml │ └── sync-ruby.yml ├── test ├── lib │ └── helper.rb └── test_forwardable.rb ├── .gitignore ├── Gemfile ├── Rakefile ├── forwardable.gemspec ├── BSDL ├── COPYING ├── README.md └── lib └── forwardable.rb /.document: -------------------------------------------------------------------------------- 1 | BSDL 2 | COPYING 3 | README.md 4 | lib/ 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - dependencies # Added by Dependabot 5 | -------------------------------------------------------------------------------- /test/lib/helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "core_assertions" 3 | 4 | Test::Unit::TestCase.include Test::Unit::CoreAssertions 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | /Gemfile.lock 10 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require_relative "../lib/forwardable" 5 | 6 | require "irb" 7 | IRB.start(__FILE__) 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem "bundler" 7 | gem "rake" 8 | gem "test-unit" 9 | gem "test-unit-ruby-core" 10 | end 11 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test/lib" 6 | t.ruby_opts << "-rhelper" 7 | t.test_files = FileList["test/**/test_*.rb"] 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /forwardable.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | name = File.basename(__FILE__, ".gemspec") 4 | version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir| 5 | break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| 6 | /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 7 | end rescue nil 8 | end 9 | 10 | Gem::Specification.new do |spec| 11 | spec.name = name 12 | spec.version = version 13 | spec.authors = ["Keiju ISHITSUKA"] 14 | spec.email = ["keiju@ruby-lang.org"] 15 | 16 | spec.summary = %q{Provides delegation of specified methods to a designated object.} 17 | spec.description = %q{Provides delegation of specified methods to a designated object.} 18 | spec.homepage = "https://github.com/ruby/forwardable" 19 | spec.licenses = ["Ruby", "BSD-2-Clause"] 20 | 21 | spec.required_ruby_version = '>= 2.4.0' 22 | spec.files = ["forwardable.gemspec", "lib/forwardable.rb"] 23 | spec.bindir = "exe" 24 | spec.executables = [] 25 | spec.require_paths = ["lib"] 26 | end 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ruby-versions: 7 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 8 | with: 9 | engine: cruby 10 | min_version: 2.4 11 | 12 | test: 13 | needs: ruby-versions 14 | name: build (${{ matrix.ruby }} / ${{ matrix.os }}) 15 | strategy: 16 | matrix: 17 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 18 | os: [ubuntu-latest, macos-latest, windows-latest] 19 | exclude: 20 | - os: macos-latest 21 | ruby: 2.4 22 | - os: macos-latest 23 | ruby: 2.5 24 | - os: windows-latest 25 | ruby: 2.4 26 | - os: windows-latest 27 | ruby: 2.5 28 | - os: windows-latest 29 | ruby: 2.6 30 | runs-on: ${{ matrix.os }} 31 | steps: 32 | - uses: actions/checkout@v6 33 | - name: Set up Ruby 34 | uses: ruby/setup-ruby@v1 35 | with: 36 | ruby-version: ${{ matrix.ruby }} 37 | bundler-cache: true 38 | - name: Run test 39 | run: bundle exec rake 40 | -------------------------------------------------------------------------------- /.github/workflows/sync-ruby.yml: -------------------------------------------------------------------------------- 1 | name: Sync ruby 2 | on: 3 | push: 4 | branches: [master] 5 | jobs: 6 | sync: 7 | name: Sync ruby 8 | runs-on: ubuntu-latest 9 | if: ${{ github.repository_owner == 'ruby' }} 10 | steps: 11 | - uses: actions/checkout@v6 12 | 13 | - name: Create GitHub App token 14 | id: app-token 15 | uses: actions/create-github-app-token@v2 16 | with: 17 | app-id: 2060836 18 | private-key: ${{ secrets.RUBY_SYNC_DEFAULT_GEMS_PRIVATE_KEY }} 19 | owner: ruby 20 | repositories: ruby 21 | 22 | - name: Sync to ruby/ruby 23 | uses: convictional/trigger-workflow-and-wait@v1.6.5 24 | with: 25 | owner: ruby 26 | repo: ruby 27 | workflow_file_name: sync_default_gems.yml 28 | github_token: ${{ steps.app-token.outputs.token }} 29 | ref: master 30 | client_payload: | 31 | {"gem":"${{ github.event.repository.name }}","before":"${{ github.event.before }}","after":"${{ github.event.after }}"} 32 | propagate_failure: true 33 | wait_interval: 10 34 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a. place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b. use the modified software only within your corporation or 18 | organization. 19 | 20 | c. give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d. make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a. distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b. accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c. give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d. make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Forwardable 2 | 3 | The Forwardable module provides delegation of specified methods to a designated object, using the methods `#def_delegator` and `#def_delegators`. 4 | 5 | ## Installation 6 | 7 | Add this line to your application's `Gemfile`: 8 | 9 | ```ruby 10 | gem 'forwardable' 11 | ``` 12 | 13 | And then execute: 14 | 15 | ```sh 16 | $ bundle 17 | ``` 18 | 19 | Or install it yourself as: 20 | 21 | ```sh 22 | $ gem install forwardable 23 | ``` 24 | 25 | ## Usage 26 | 27 | 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: 28 | 29 | ```ruby 30 | require 'forwardable' 31 | 32 | class RecordCollection 33 | attr_accessor :records 34 | extend Forwardable 35 | def_delegator :@records, :[], :record_number 36 | end 37 | ``` 38 | 39 | We can use the lookup method like so: 40 | 41 | ```ruby 42 | r = RecordCollection.new 43 | r.records = [4,5,6] 44 | r.record_number(0) # => 4 45 | ``` 46 | 47 | Further, if you wish to provide the methods `#size`, `#<<`, and `#map`, all of which delegate to `@records`, this is how you can do it: 48 | 49 | ```ruby 50 | class RecordCollection # re-open RecordCollection class 51 | def_delegators :@records, :size, :<<, :map 52 | end 53 | 54 | r = RecordCollection.new 55 | r.records = [1,2,3] 56 | r.record_number(0) # => 1 57 | r.size # => 3 58 | r << 4 # => [1, 2, 3, 4] 59 | r.map { |x| x * 2 } # => [2, 4, 6, 8] 60 | ``` 61 | 62 | You can even extend regular objects with Forwardable. 63 | 64 | ```ruby 65 | my_hash = Hash.new 66 | my_hash.extend Forwardable # prepare object for delegation 67 | my_hash.def_delegator "STDOUT", "puts" # add delegation for STDOUT.puts() 68 | my_hash.puts "Howdy!" 69 | ``` 70 | 71 | ## Development 72 | 73 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 74 | 75 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 76 | 77 | ## Contributing 78 | 79 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/forwardable. 80 | 81 | ## License 82 | 83 | The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). 84 | -------------------------------------------------------------------------------- /lib/forwardable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # 3 | # forwardable.rb - 4 | # $Release Version: 1.1$ 5 | # $Revision$ 6 | # by Keiju ISHITSUKA(keiju@ishitsuka.com) 7 | # original definition by delegator.rb 8 | # Revised by Daniel J. Berger with suggestions from Florian Gross. 9 | # 10 | # Documentation by James Edward Gray II and Gavin Sinclair 11 | 12 | 13 | 14 | # The Forwardable module provides delegation of specified 15 | # methods to a designated object, using the methods #def_delegator 16 | # and #def_delegators. 17 | # 18 | # For example, say you have a class RecordCollection which 19 | # contains an array @records. You could provide the lookup method 20 | # #record_number(), which simply calls #[] on the @records 21 | # array, like this: 22 | # 23 | # require 'forwardable' 24 | # 25 | # class RecordCollection 26 | # attr_accessor :records 27 | # extend Forwardable 28 | # def_delegator :@records, :[], :record_number 29 | # end 30 | # 31 | # We can use the lookup method like so: 32 | # 33 | # r = RecordCollection.new 34 | # r.records = [4,5,6] 35 | # r.record_number(0) # => 4 36 | # 37 | # Further, if you wish to provide the methods #size, #<<, and #map, 38 | # all of which delegate to @records, this is how you can do it: 39 | # 40 | # class RecordCollection # re-open RecordCollection class 41 | # def_delegators :@records, :size, :<<, :map 42 | # end 43 | # 44 | # r = RecordCollection.new 45 | # r.records = [1,2,3] 46 | # r.record_number(0) # => 1 47 | # r.size # => 3 48 | # r << 4 # => [1, 2, 3, 4] 49 | # r.map { |x| x * 2 } # => [2, 4, 6, 8] 50 | # 51 | # You can even extend regular objects with Forwardable. 52 | # 53 | # my_hash = Hash.new 54 | # my_hash.extend Forwardable # prepare object for delegation 55 | # my_hash.def_delegator "STDOUT", "puts" # add delegation for STDOUT.puts() 56 | # my_hash.puts "Howdy!" 57 | # 58 | # == Another example 59 | # 60 | # You could use Forwardable as an alternative to inheritance, when you don't want 61 | # to inherit all methods from the superclass. For instance, here is how you might 62 | # add a range of +Array+ instance methods to a new class +Queue+: 63 | # 64 | # class Queue 65 | # extend Forwardable 66 | # 67 | # def initialize 68 | # @q = [ ] # prepare delegate object 69 | # end 70 | # 71 | # # setup preferred interface, enq() and deq()... 72 | # def_delegator :@q, :push, :enq 73 | # def_delegator :@q, :shift, :deq 74 | # 75 | # # support some general Array methods that fit Queues well 76 | # def_delegators :@q, :clear, :first, :push, :shift, :size 77 | # end 78 | # 79 | # q = Thread::Queue.new 80 | # q.enq 1, 2, 3, 4, 5 81 | # q.push 6 82 | # 83 | # q.shift # => 1 84 | # while q.size > 0 85 | # puts q.deq 86 | # end 87 | # 88 | # q.enq "Ruby", "Perl", "Python" 89 | # puts q.first 90 | # q.clear 91 | # puts q.first 92 | # 93 | # This should output: 94 | # 95 | # 2 96 | # 3 97 | # 4 98 | # 5 99 | # 6 100 | # Ruby 101 | # nil 102 | # 103 | # == Notes 104 | # 105 | # Be advised, RDoc will not detect delegated methods. 106 | # 107 | # +forwardable.rb+ provides single-method delegation via the def_delegator and 108 | # def_delegators methods. For full-class delegation via DelegateClass, see 109 | # +delegate.rb+. 110 | # 111 | module Forwardable 112 | # Version of +forwardable.rb+ 113 | VERSION = "1.4.0" 114 | VERSION.freeze 115 | 116 | # Version for backward compatibility 117 | FORWARDABLE_VERSION = VERSION 118 | FORWARDABLE_VERSION.freeze 119 | 120 | @debug = nil 121 | class << self 122 | # ignored 123 | attr_accessor :debug 124 | end 125 | 126 | # Takes a hash as its argument. The key is a symbol or an array of 127 | # symbols. These symbols correspond to method names, instance variable 128 | # names, or constant names (see def_delegator). The value is 129 | # the accessor to which the methods will be delegated. 130 | # 131 | # :call-seq: 132 | # delegate method => accessor 133 | # delegate [method, method, ...] => accessor 134 | # 135 | def instance_delegate(hash) 136 | hash.each do |methods, accessor| 137 | unless defined?(methods.each) 138 | def_instance_delegator(accessor, methods) 139 | else 140 | methods.each {|method| def_instance_delegator(accessor, method)} 141 | end 142 | end 143 | end 144 | 145 | # 146 | # Shortcut for defining multiple delegator methods, but with no 147 | # provision for using a different name. The following two code 148 | # samples have the same effect: 149 | # 150 | # def_delegators :@records, :size, :<<, :map 151 | # 152 | # def_delegator :@records, :size 153 | # def_delegator :@records, :<< 154 | # def_delegator :@records, :map 155 | # 156 | def def_instance_delegators(accessor, *methods) 157 | methods.each do |method| 158 | next if /\A__(?:send|id)__\z/ =~ method 159 | def_instance_delegator(accessor, method) 160 | end 161 | end 162 | 163 | # Define +method+ as delegator instance method with an optional 164 | # alias name +ali+. Method calls to +ali+ will be delegated to 165 | # +accessor.method+. +accessor+ should be a method name, instance 166 | # variable name, or constant name. Use the full path to the 167 | # constant if providing the constant name. 168 | # Returns the name of the method defined. 169 | # 170 | # class MyQueue 171 | # CONST = 1 172 | # extend Forwardable 173 | # attr_reader :queue 174 | # def initialize 175 | # @queue = [] 176 | # end 177 | # 178 | # def_delegator :@queue, :push, :mypush 179 | # def_delegator 'MyQueue::CONST', :to_i 180 | # end 181 | # 182 | # q = MyQueue.new 183 | # q.mypush 42 184 | # q.queue #=> [42] 185 | # q.push 23 #=> NoMethodError 186 | # q.to_i #=> 1 187 | # 188 | def def_instance_delegator(accessor, method, ali = method) 189 | gen = Forwardable._delegator_method(self, accessor, method, ali) 190 | 191 | # If it's not a class or module, it's an instance 192 | mod = Module === self ? self : singleton_class 193 | mod.module_eval(&gen) 194 | end 195 | 196 | alias delegate instance_delegate 197 | alias def_delegators def_instance_delegators 198 | alias def_delegator def_instance_delegator 199 | 200 | # :nodoc: 201 | def self._delegator_method(obj, accessor, method, ali) 202 | accessor = accessor.to_s unless Symbol === accessor 203 | 204 | if Module === obj ? 205 | obj.method_defined?(accessor) || obj.private_method_defined?(accessor) : 206 | obj.respond_to?(accessor, true) 207 | accessor = "(#{accessor}())" 208 | end 209 | 210 | args = RUBY_VERSION >= '2.7' ? '...' : '*args, &block' 211 | method_call = ".__send__(:#{method}, #{args})" 212 | if method.match?(/\A[_a-zA-Z]\w*[?!]?\z/) 213 | loc, = caller_locations(2,1) 214 | pre = "_ =" 215 | mesg = "#{Module === obj ? obj : obj.class}\##{ali} at #{loc.path}:#{loc.lineno} forwarding to private method " 216 | method_call = <<~RUBY.chomp 217 | if defined?(_.#{method}) 218 | _.#{method}(#{args}) 219 | else 220 | ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1 221 | _#{method_call} 222 | end 223 | RUBY 224 | end 225 | 226 | eval(<<~RUBY, nil, __FILE__, __LINE__ + 1) 227 | proc do 228 | def #{ali}(#{args}) 229 | #{pre}#{accessor} 230 | #{method_call} 231 | end 232 | end 233 | RUBY 234 | end 235 | end 236 | 237 | # SingleForwardable can be used to setup delegation at the object level as well. 238 | # 239 | # printer = String.new 240 | # printer.extend SingleForwardable # prepare object for delegation 241 | # printer.def_delegator "STDOUT", "puts" # add delegation for STDOUT.puts() 242 | # printer.puts "Howdy!" 243 | # 244 | # Also, SingleForwardable can be used to set up delegation for a Class or Module. 245 | # 246 | # class Implementation 247 | # def self.service 248 | # puts "serviced!" 249 | # end 250 | # end 251 | # 252 | # module Facade 253 | # extend SingleForwardable 254 | # def_delegator :Implementation, :service 255 | # end 256 | # 257 | # Facade.service #=> serviced! 258 | # 259 | # If you want to use both Forwardable and SingleForwardable, you can 260 | # use methods def_instance_delegator and def_single_delegator, etc. 261 | module SingleForwardable 262 | # Takes a hash as its argument. The key is a symbol or an array of 263 | # symbols. These symbols correspond to method names. The value is 264 | # the accessor to which the methods will be delegated. 265 | # 266 | # :call-seq: 267 | # delegate method => accessor 268 | # delegate [method, method, ...] => accessor 269 | # 270 | def single_delegate(hash) 271 | hash.each do |methods, accessor| 272 | unless defined?(methods.each) 273 | def_single_delegator(accessor, methods) 274 | else 275 | methods.each {|method| def_single_delegator(accessor, method)} 276 | end 277 | end 278 | end 279 | 280 | # 281 | # Shortcut for defining multiple delegator methods, but with no 282 | # provision for using a different name. The following two code 283 | # samples have the same effect: 284 | # 285 | # def_delegators :@records, :size, :<<, :map 286 | # 287 | # def_delegator :@records, :size 288 | # def_delegator :@records, :<< 289 | # def_delegator :@records, :map 290 | # 291 | def def_single_delegators(accessor, *methods) 292 | methods.each do |method| 293 | next if /\A__(?:send|id)__\z/ =~ method 294 | def_single_delegator(accessor, method) 295 | end 296 | end 297 | 298 | # :call-seq: 299 | # def_single_delegator(accessor, method, new_name=method) 300 | # 301 | # Defines a method _method_ which delegates to _accessor_ (i.e. it calls 302 | # the method of the same name in _accessor_). If _new_name_ is 303 | # provided, it is used as the name for the delegate method. 304 | # Returns the name of the method defined. 305 | def def_single_delegator(accessor, method, ali = method) 306 | gen = Forwardable._delegator_method(self, accessor, method, ali) 307 | 308 | instance_eval(&gen) 309 | end 310 | 311 | alias delegate single_delegate 312 | alias def_delegators def_single_delegators 313 | alias def_delegator def_single_delegator 314 | end 315 | -------------------------------------------------------------------------------- /test/test_forwardable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'forwardable' 4 | 5 | class TestForwardable < Test::Unit::TestCase 6 | INTEGER = 42 7 | RECEIVER = BasicObject.new 8 | RETURNED1 = BasicObject.new 9 | RETURNED2 = BasicObject.new 10 | 11 | class << RECEIVER 12 | def delegated1 13 | RETURNED1 14 | end 15 | 16 | def delegated2 17 | RETURNED2 18 | end 19 | 20 | def delegated1_kw(**kw) 21 | [RETURNED1, kw] 22 | end 23 | end 24 | 25 | def test_def_instance_delegator 26 | %i[def_delegator def_instance_delegator].each do |m| 27 | ret = nil 28 | cls = forwardable_class do 29 | ret = __send__ m, :@receiver, :delegated1 30 | end 31 | 32 | assert_same RETURNED1, cls.new.delegated1 33 | assert_equal :delegated1, ret 34 | end 35 | end 36 | 37 | def test_def_instance_delegator_constant 38 | %i[def_delegator def_instance_delegator].each do |m| 39 | cls = forwardable_class do 40 | __send__ m, 'TestForwardable::INTEGER', :to_i 41 | end 42 | 43 | assert_equal 42, cls.new.to_i 44 | end 45 | end 46 | 47 | def test_def_instance_delegator_kw 48 | %i[def_delegator def_instance_delegator].each do |m| 49 | cls = forwardable_class do 50 | __send__ m, :@receiver, :delegated1_kw 51 | end 52 | 53 | ary = cls.new.delegated1_kw b: 1 54 | assert_same RETURNED1, ary[0] 55 | assert_equal({b: 1}, ary[1]) 56 | end 57 | end 58 | 59 | def test_def_instance_delegator_using_args_method_as_receiver 60 | %i[def_delegator def_instance_delegator].each do |m| 61 | cls = forwardable_class( 62 | receiver_name: :args, 63 | type: :method, 64 | visibility: :private 65 | ) do 66 | __send__ m, :args, :delegated1 67 | end 68 | 69 | assert_same RETURNED1, cls.new.delegated1 70 | end 71 | end 72 | 73 | def test_def_instance_delegator_using_block_method_as_receiver 74 | %i[def_delegator def_instance_delegator].each do |m| 75 | cls = forwardable_class( 76 | receiver_name: :block, 77 | type: :method, 78 | visibility: :private 79 | ) do 80 | __send__ m, :block, :delegated1 81 | end 82 | 83 | assert_same RETURNED1, cls.new.delegated1 84 | end 85 | end 86 | 87 | def test_def_instance_delegators 88 | %i[def_delegators def_instance_delegators].each do |m| 89 | cls = forwardable_class do 90 | __send__ m, :@receiver, :delegated1, :delegated2 91 | end 92 | 93 | assert_same RETURNED1, cls.new.delegated1 94 | assert_same RETURNED2, cls.new.delegated2 95 | end 96 | end 97 | 98 | def test_def_instance_delegators_using_args_method_as_receiver 99 | %i[def_delegators def_instance_delegators].each do |m| 100 | cls = forwardable_class( 101 | receiver_name: :args, 102 | type: :method, 103 | visibility: :private 104 | ) do 105 | __send__ m, :args, :delegated1, :delegated2 106 | end 107 | 108 | assert_same RETURNED1, cls.new.delegated1 109 | assert_same RETURNED2, cls.new.delegated2 110 | end 111 | end 112 | 113 | def test_def_instance_delegators_using_block_method_as_receiver 114 | %i[def_delegators def_instance_delegators].each do |m| 115 | cls = forwardable_class( 116 | receiver_name: :block, 117 | type: :method, 118 | visibility: :private 119 | ) do 120 | __send__ m, :block, :delegated1, :delegated2 121 | end 122 | 123 | assert_same RETURNED1, cls.new.delegated1 124 | assert_same RETURNED2, cls.new.delegated2 125 | end 126 | end 127 | 128 | def test_def_instance_delegators_send_id 129 | %i[def_delegators def_instance_delegators].each do |m| 130 | cls = forwardable_class do 131 | attr_reader :receiver 132 | __send__ m, :@receiver, :__send__, :__id__ 133 | end 134 | 135 | assert_not_equal cls.new.__id__, cls.new.receiver.__id__ 136 | assert_not_equal cls.new.__send__(:__id__), cls.new.receiver.__send__(:__id__) 137 | end 138 | end 139 | 140 | def test_instance_delegate 141 | %i[delegate instance_delegate].each do |m| 142 | cls = forwardable_class do 143 | __send__ m, delegated1: :@receiver, delegated2: :@receiver 144 | end 145 | 146 | assert_same RETURNED1, cls.new.delegated1 147 | assert_same RETURNED2, cls.new.delegated2 148 | 149 | cls = forwardable_class do 150 | __send__ m, %i[delegated1 delegated2] => :@receiver 151 | end 152 | 153 | assert_same RETURNED1, cls.new.delegated1 154 | assert_same RETURNED2, cls.new.delegated2 155 | end 156 | end 157 | 158 | def test_def_instance_delegate_using_args_method_as_receiver 159 | %i[delegate instance_delegate].each do |m| 160 | cls = forwardable_class( 161 | receiver_name: :args, 162 | type: :method, 163 | visibility: :private 164 | ) do 165 | __send__ m, delegated1: :args, delegated2: :args 166 | end 167 | 168 | assert_same RETURNED1, cls.new.delegated1 169 | assert_same RETURNED2, cls.new.delegated2 170 | end 171 | end 172 | 173 | def test_def_instance_delegate_using_block_method_as_receiver 174 | %i[delegate instance_delegate].each do |m| 175 | cls = forwardable_class( 176 | receiver_name: :block, 177 | type: :method, 178 | visibility: :private 179 | ) do 180 | __send__ m, delegated1: :block, delegated2: :block 181 | end 182 | 183 | assert_same RETURNED1, cls.new.delegated1 184 | assert_same RETURNED2, cls.new.delegated2 185 | end 186 | end 187 | 188 | def test_class_single_delegator 189 | %i[def_delegator def_single_delegator].each do |m| 190 | ret = nil 191 | cls = single_forwardable_class do 192 | ret = __send__ m, :@receiver, :delegated1 193 | end 194 | 195 | assert_same RETURNED1, cls.delegated1 196 | assert_equal :delegated1, ret 197 | end 198 | end 199 | 200 | def test_class_single_delegators 201 | %i[def_delegators def_single_delegators].each do |m| 202 | cls = single_forwardable_class do 203 | __send__ m, :@receiver, :delegated1, :delegated2 204 | end 205 | 206 | assert_same RETURNED1, cls.delegated1 207 | assert_same RETURNED2, cls.delegated2 208 | end 209 | end 210 | 211 | def test_class_single_delegate 212 | %i[delegate single_delegate].each do |m| 213 | cls = single_forwardable_class do 214 | __send__ m, delegated1: :@receiver, delegated2: :@receiver 215 | end 216 | 217 | assert_same RETURNED1, cls.delegated1 218 | assert_same RETURNED2, cls.delegated2 219 | 220 | cls = single_forwardable_class do 221 | __send__ m, %i[delegated1 delegated2] => :@receiver 222 | end 223 | 224 | assert_same RETURNED1, cls.delegated1 225 | assert_same RETURNED2, cls.delegated2 226 | end 227 | end 228 | 229 | def test_obj_single_delegator 230 | %i[def_delegator def_single_delegator].each do |m| 231 | obj = single_forwardable_object do 232 | __send__ m, :@receiver, :delegated1 233 | end 234 | 235 | assert_same RETURNED1, obj.delegated1 236 | end 237 | end 238 | 239 | def test_obj_single_delegators 240 | %i[def_delegators def_single_delegators].each do |m| 241 | obj = single_forwardable_object do 242 | __send__ m, :@receiver, :delegated1, :delegated2 243 | end 244 | 245 | assert_same RETURNED1, obj.delegated1 246 | assert_same RETURNED2, obj.delegated2 247 | end 248 | end 249 | 250 | def test_obj_single_delegators_send_id 251 | %i[def_delegators def_single_delegators].each do |m| 252 | obj = single_forwardable_object do 253 | singleton_class.__send__ :attr_reader, :receiver 254 | __send__ m, :@receiver, :__send__, :__id__ 255 | end 256 | 257 | assert_not_equal obj.__id__, obj.receiver.__id__ 258 | assert_not_equal obj.__send__(:__id__), obj.receiver.__send__(:__id__) 259 | end 260 | end 261 | 262 | def test_obj_single_delegate 263 | %i[delegate single_delegate].each do |m| 264 | obj = single_forwardable_object do 265 | __send__ m, delegated1: :@receiver, delegated2: :@receiver 266 | end 267 | 268 | assert_same RETURNED1, obj.delegated1 269 | assert_same RETURNED2, obj.delegated2 270 | 271 | obj = single_forwardable_object do 272 | __send__ m, %i[delegated1 delegated2] => :@receiver 273 | end 274 | 275 | assert_same RETURNED1, obj.delegated1 276 | assert_same RETURNED2, obj.delegated2 277 | end 278 | end 279 | 280 | class Foo 281 | extend Forwardable 282 | 283 | attr_accessor :bar 284 | def_delegator :bar, :baz 285 | def_delegator :caller, :itself, :c 286 | end 287 | 288 | def test_backtrace_adjustment 289 | obj = Foo.new 290 | def (obj.bar = Object.new).baz 291 | foo 292 | end 293 | e = assert_raise(NameError) { 294 | obj.baz 295 | } 296 | assert_not_match(/\/forwardable\.rb/, e.backtrace[0], 297 | proc {RubyVM::InstructionSequence.of(obj.method(:baz)).disassemble}) 298 | assert_equal(caller(0, 1)[0], Foo.new.c[0]) 299 | end 300 | 301 | class Foo2 < BasicObject 302 | extend ::Forwardable 303 | 304 | def_delegator :bar, :baz 305 | end 306 | 307 | def test_basicobject_subclass 308 | bug11616 = '[ruby-core:71176] [Bug #11616]' 309 | assert_raise_with_message(NameError, /[`']bar'/, bug11616) { 310 | Foo2.new.baz 311 | } 312 | end 313 | 314 | def test_aref 315 | obj = Object.new.extend SingleForwardable 316 | obj.instance_variable_set("@h", {foo: 42}) 317 | obj.def_delegator("@h", :[]) 318 | assert_equal(42, obj[:foo]) 319 | end 320 | 321 | def test_aset 322 | obj = Object.new.extend SingleForwardable 323 | obj.instance_variable_set("@h", h = {foo: 0}) 324 | obj.def_delegator("@h", :[]=) 325 | obj[:foo] = 42 326 | assert_equal(42, h[:foo]) 327 | end 328 | 329 | def test_binop 330 | obj = Object.new.extend SingleForwardable 331 | obj.instance_variable_set("@h", 40) 332 | obj.def_delegator("@h", :+) 333 | assert_equal(42, obj+2) 334 | end 335 | 336 | def test_uniop 337 | obj = Object.new.extend SingleForwardable 338 | obj.instance_variable_set("@h", -42) 339 | obj.def_delegator("@h", :-@) 340 | assert_equal(42, -obj) 341 | end 342 | 343 | def test_on_private_method 344 | cls = Class.new do 345 | private def foo; :foo end 346 | extend Forwardable 347 | def_delegator :itself, :foo, :bar 348 | end 349 | assert_warn(/forwarding to private method/) do 350 | assert_equal(:foo, cls.new.bar) 351 | end 352 | end 353 | 354 | def test_non_module 355 | str = String.new 356 | str.extend Forwardable 357 | str.instance_variable_set("@h", 42) 358 | str.def_delegator("@h", :to_s, :forty_two) 359 | assert_equal("42", str.forty_two) 360 | end 361 | 362 | private 363 | 364 | def forwardable_class( 365 | receiver_name: :receiver, 366 | type: :ivar, 367 | visibility: :public, 368 | &block 369 | ) 370 | Class.new do 371 | extend Forwardable 372 | 373 | define_method(:initialize) do 374 | instance_variable_set("@#{receiver_name}", RECEIVER) 375 | end 376 | 377 | if type == :method 378 | attr_reader(receiver_name) 379 | __send__(visibility, receiver_name) 380 | end 381 | 382 | class_exec(&block) 383 | end 384 | end 385 | 386 | def single_forwardable_class(&block) 387 | Class.new do 388 | extend SingleForwardable 389 | 390 | @receiver = RECEIVER 391 | 392 | class_exec(&block) 393 | end 394 | end 395 | 396 | def single_forwardable_object(&block) 397 | obj = Object.new.extend SingleForwardable 398 | obj.instance_variable_set(:@receiver, RECEIVER) 399 | obj.instance_eval(&block) 400 | obj 401 | end 402 | end 403 | --------------------------------------------------------------------------------