Problem
You'd like to delegate some of an object's method calls to a different object, or make one object capable of " impersonating" another.
Solution
If you want to completely impersonate another object, or delegate most of one object's calls to another, use the delegate library. It generates custom classes whose instances can impersonate objects of any other class. These custom classes respond to all methods of the class they shadow, but they don't do any work of their own apart from calling the same method on some instance of the "real" class.
Here's some code that uses delegate to generate CardinalNumber, a class that acts almost like a Fixnum. CardinalNumber defines the same methods as Fixnum does, and it takes a genuine Fixnum as an argument to its constructor. It stores this object as a member, and when you call any of Fixnum's methods on a CardinalNumber object, it delegates that method call to the stored Fixnum. The only major exception is the to_s method, which I've decided to override.
require 'delegate' # An integer represented as an ordinal number (1st, 2nd, 3rd…), as # opposed to an ordinal number (1, 2, 3…) Generated by the # DelegateClass to have all the methods of the Fixnum class. class OrdinalNumber < DelegateClass(Fixnum) def to_s delegate_s = __getobj_ _.to_s check = abs if to_check == 11 or to_check == 12 suffix = "th" else case check % 10 when 1 then suffix = "st" when 2 then suffix = "nd" else suffix = "th" end end return delegate_s + suffix end end 4.to_s # => "4" OrdinalNumber.new(4).to_s # => "4th" OrdinalNumber.new(102).to_s # => "102nd" OrdinalNumber.new(11).to_s # => "11th" OrdinalNumber.new(-21).to_s # => "-21st" OrdinalNumber.new(5).succ # => 6 OrdinalNumber.new(5) + 6 # => 11 OrdinalNumber.new(5) + OrdinalNumber.new(6) # => 11
Discussion
The delegate library is useful when you want to extend the behavior of objects you don't have much control over. Usually these are objects you're not in charge of instantiatingthey're instantiated by factory methods, or by Ruby itself. With delegate, you can create a class that wraps an already existing object of another class and modifies its behavior. You can do all of this without changing the original class. This is especially useful if the original class has been frozen.
There are a few methods that delegate won't delegate: most of the ones in Kernel. public_instance_methods. The most important one is is_a?. Code that explicitly checks the type of your object will be able to see that it's not a real instance of the object it's impersonating. Using is_a? instead of respond_to? is often bad Ruby practice, but it happens pretty often, so you should be aware of it.
The Forwardable module is a little more precise and a little less discerning: it lets you delegate any of an object's methods to another object. A class that extends Forwardable can use the def_delegator decorator method, which takes as arguments an object symbol and a method symbol. It defines a new method that delegates to the method of the same name in the given object. There's also a def_delegators method, which takes multiple method symbols as arguments and defines a delegator method for each one. By calling def_delegator multiple times, you can have a single Forwardable delegate different methods to different subobjects.
Here I'll use Forwardable to define a simple class that works like an array, but supports none of Array's methods except the append operator, <<. Note how the << method defined by def_delegator is passed through to modify the underlying array.
class AppendOnlyArray extend Forwardable def initialize @array = [] end def_delegator :@array, :<< end a = AppendOnlyArray a << 4 a << 5 a.size # => undefined method 'size' for #
AppendOnlyArray is pretty useless, but the same principle makes Forwardable useful if you want to expose only a portion of a class' interface. For instance, suppose you want to create a data structure that works like a Hash, but only supports random access. You don't want to support keys, each, or any of the other ways of getting information out of a hash without providing a key.
You could subclass Hash, then redefine or delete all the methods that you don't want to support. Then you could worry a lot about having missed some of those methods. Or you could define a subclass of Forwardable and define only the methods of Hash that you do want to support.
class RandomAccessHash extend Forwardable def initialize @delegate_to = {} end def_delegators :@delegate_to, :[], "[]=" end balances_by_account_number = RandomAccessHash.new # Load balances from a database or something. balances_by_account_number["101240A"] = 412.60 balances_by_account_number["104918J"] = 10339.94 balances_by_account_number["108826N"] = 293.01
Random access works if you know the key, but anything else is forbidden:
balances_by_account_number["104918J"] # => 10339.94 balances_by_account_number.each do |number, balance| puts "I now know the balance for account #{number}: it's #{balance}" end # => NoMethodError: undefined method 'each' for #
See Also
Strings
Numbers
Date and Time
Arrays
Hashes
Files and Directories
Code Blocks and Iteration
Objects and Classes8
Modules and Namespaces
Reflection and Metaprogramming
XML and HTML
Graphics and Other File Formats
Databases and Persistence
Internet Services
Web Development Ruby on Rails
Web Services and Distributed Programming
Testing, Debugging, Optimizing, and Documenting
Packaging and Distributing Software
Automating Tasks with Rake
Multitasking and Multithreading
User Interface
Extending Ruby with Other Languages
System Administration