
To delegate a method in Ruby, forward the call to another object. Write the forwarding method yourself, or generate it: extend Forwardable plus def_delegator :@target, :name in plain Ruby, or delegate :name, to: :target in Rails. To forward every method you don’t define, wrap the object in a SimpleDelegator subclass.
That is the short version. This guide covers each technique in turn: explicit forwarding, Forwardable, the Delegator classes (SimpleDelegator and DelegateClass), a hand-rolled method_missing, and Rails’ delegate with its prefix:, allow_nil:, and private: options. It then covers the Casting gem, followed by the seven error strings delegation produces and their fixes. Every example and every error message was run on Ruby 3.4.10 and Rails 8.1.3.1 (forwardable 1.3.3, delegate 0.4.0, casting 1.0.3).
Delegation in Ruby
Strictly speaking, delegation means evaluating a method of one object in the context of another: the receiving object supplies the method, but self still refers to the original sender. What most Ruby code calls delegation is the looser pattern, forwarding: an object calls the corresponding method on another object it holds, without passing itself along. Ruby developers use the word “delegation” for both, and so does this guide. The Casting gem, covered near the end, is the one tool here that implements the strict form.
The payoff: a wrapper that forwards a chosen set of methods exposes exactly the interface you want, without inheriting from the wrapped class. A UserDecorator can present a User to a view without becoming a User.
Explicit Delegation
The plainest form of delegation is a method that calls the same method on another object. A Printer that hands the work to a specific printer model looks like this:
class HP
def print_page(text)
# Send the text to the device
end
end
class Printer
def initialize(printer_model)
@printer_model = printer_model
end
def print_page(text)
@printer_model.print_page(text)
end
end
Printer.new(HP.new).print_page("Hello")Printer#print_page forwards the call to HP#print_page. The printing logic lives in one place, and Printer stays readable: anyone opening the file sees where the call goes.
Explicit delegation is the right choice when you forward one to three methods, when you build an adapter around a third-party client and want the boundary visible, and anywhere a reader should see the forwarding rather than infer it from a macro. Its failure mode is the ordinary one: if @printer_model is nil, the call raises undefined method 'print_page' for nil (NoMethodError), which the exceptions guide covers under undefined method for nil.
Ruby’s Forwardable Module
Forwardable is a default gem (forwardable 1.3.3 ships with Ruby 3.4.10), so require "forwardable" always works. You extend it into a class, never include it, and then declare which methods to forward. Each declaration defines a real method on the class: instance_methods(false) lists it, and respond_to? returns true without any extra code.
Delegating Methods with def_delegator and def_delegators
def_delegator forwards one method to an accessor, which can be an instance variable (:@formatter) or a method name. Here Printer forwards render to a Formatter:
require "forwardable"
class Formatter
def render(text)
text.strip
end
def emphasize(text)
"*#{text}*"
end
end
class Printer
extend Forwardable
def_delegator :@formatter, :render
def initialize(formatter)
@formatter = formatter
end
def print_page(text)
puts render(text)
end
end
Printer.new(Formatter.new).print_page(" Hello ")
# HelloTo forward several methods at once, def_delegators takes the accessor followed by every method name:
class Printer
extend Forwardable
def_delegators :@formatter, :render, :emphasize
end
Printer.instance_methods(false).sort # => [:emphasize, :print_page, :render]
Printer.new(Formatter.new).emphasize("Hello") # => "*Hello*"Three things to keep in mind:
- The target must be assigned before the first forwarded call. A
niltarget does not fail cleanly: Ruby prints a misleadingforwarding to private method NilClass#renderwarning and then raisesNoMethodError(see the warning section). - Readability drops as the list grows. A class that forwards 15 methods no longer shows which behavior is its own.
- Each forwarded call costs about three times what an explicit call costs in the benchmark at the end of this guide. That is still well under a microsecond, so it matters only in hot loops.
Renaming Delegated Methods and Hiding the Wrapped Object
Because Forwardable forwards only what you list, it doubles as an access-control tool. This UserDecorator exposes first_name and last_name from the wrapped User and nothing else:
require "forwardable"
User = Struct.new(:first_name, :last_name)
class UserDecorator
extend Forwardable
def_delegators :@user, :first_name, :last_name
def initialize(user)
@user = user
end
def full_name
"#{first_name} #{last_name}"
end
end
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.full_name # => "John Doe"User#to_a, User#members, and every other Struct method stay hidden behind the decorator. That control over the public interface is why many Ruby developers prefer Forwardable to SimpleDelegator, which forwards everything.
def_delegator also takes an optional third argument, the name the method gets on the wrapper:
class UserDecorator
extend Forwardable
def_delegator :@user, :first_name, :personal_name
def_delegator :@user, :last_name, :family_name
def initialize(user)
@user = user
end
def full_name
"#{personal_name} #{family_name}"
end
end
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.personal_name # => "John"Only the aliases exist on the wrapper: decorated_user.first_name raises undefined method 'first_name' for an instance of UserDecorator (NoMethodError). Renaming earns its keep when you migrate between libraries with similar interfaces but different method names.
def_instance_delegator, instance_delegate, and SingleForwardable
Forwardable has a second, longer set of names for the same operations. def_delegator is an alias of def_instance_delegator, def_delegators of def_instance_delegators, and instance_delegate takes a hash that maps method names to an accessor:
class UserDecorator
extend Forwardable
def_instance_delegator :@user, :first_name
instance_delegate [:last_name, :to_a] => :@user
def initialize(user)
@user = user
end
end
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.to_a # => ["John", "Doe"]
Forwardable.instance_method(:def_delegator) == Forwardable.instance_method(:def_instance_delegator) # => trueinstance_delegate has an alias too: delegate. That Forwardable#delegate takes a hash and has nothing to do with Rails’ Module#delegate, which takes to:; a class that mixes both APIs reads ambiguously, so pick one per class.
SingleForwardable does the same job for a single object rather than a class. Extend an object with it and declare delegators on that object:
require "forwardable"
printer = String.new
printer.extend SingleForwardable
printer.def_delegator "STDOUT", "puts"
printer.puts "Howdy!"
# Howdy!The same mechanism gives a module a facade over a class, which is the usual reason to reach for it:
module Users
extend SingleForwardable
def_delegator :User, :new, :build
end
Users.build("John", "Doe") # => #<struct User first_name="John", last_name="Doe">def_single_delegator is the long name of SingleForwardable#def_delegator.
undefined method 'def_delegator' for class Printer (NoMethodError)
require "forwardable"
class Printer
include Forwardable
def_delegator :@formatter, :render
end$ ruby /app/printer.rb
/app/printer.rb:6:in '<class:Printer>': undefined method 'def_delegator' for class Printer (NoMethodError)
def_delegator :@formatter, :render
^^^^^^^^^^^^^
from /app/printer.rb:3:in '<main>'
Cause: include Forwardable adds def_delegator to instances of Printer, but the declaration runs in the class body, where the receiver is the class itself.
Fix: extend Forwardable. Extending puts the methods on the class object, which is where the class body calls them.
warning: forwarding to private method Formatter#render
require "forwardable"
class Formatter
private
def render(text)
text.strip
end
end
class Printer
extend Forwardable
def_delegator :@formatter, :render
def initialize(formatter)
@formatter = formatter
end
end
puts Printer.new(Formatter.new).render(" Hello ")$ ruby /app/printer.rb
/app/printer.rb:21: warning: Printer#render at /app/printer.rb:14 forwarding to private method Formatter#render
Hello
Cause: The forwarded method is private on the target. Forwardable still delivers the call, falling back to __send__, but it warns at the default $VERBOSE level; only ruby -W0 silences it. The same warning appears, misleadingly, when the target is nil: forwarding to private method NilClass#render, followed by the real problem, undefined method 'render' for nil (NoMethodError).
Fix: Make the method public on the target, or, for the nil case, assign the target before the first forwarded call.
Ruby’s Delegator Classes: SimpleDelegator and DelegateClass
Parts of this section first appeared in Michael Kohl’s 2019 Ruby Magic post on Delegator and Forwardable.
Delegator lives in the delegate default gem (delegate 0.4.0 on Ruby 3.4.10; require "delegate" loads it). It is a BasicObject subclass, so it starts with almost no methods of its own, and it forwards everything it does not define to a wrapped object through method_missing. The library ships two ways to use it: SimpleDelegator, a ready-made subclass, and DelegateClass, a method that generates a subclass for one specific target class.
Wrapping an Object with SimpleDelegator
SimpleDelegator wraps whatever you pass to new and forwards every method it does not have:
require "delegate"
User = Struct.new(:first_name, :last_name)
class UserDecorator < SimpleDelegator
def full_name
"#{first_name} #{last_name}"
end
end
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.full_name # => "John Doe"Neither first_name nor last_name is defined on UserDecorator, so both calls go to the wrapped User. This is the decorator (or presenter) pattern most Rails codebases mean when they say “decorator”.
You can override a forwarded method and still reach the original through super:
class UserDecorator < SimpleDelegator
def first_name
"#{super[0]}."
end
end
decorated_user.first_name # => "J."
decorated_user.full_name # => "J. Doe"__getobj__ returns the wrapped object, and __setobj__ swaps it for another one:
decorated_user.__getobj__ # => #<struct User first_name="John", last_name="Doe">
decorated_user.__setobj__(User.new("Jane", "Roe"))
decorated_user.full_name # => "J. Roe"SimpleDelegator Gotchas: Equality, is_a?, and Pattern Matching
A SimpleDelegator quacks like the wrapped object but is not that object, and the seams show whenever identity matters:
user = User.new("John", "Doe")
decorator = UserDecorator.new(user)
decorator == user # => true
user == decorator # => false
[user].include?(decorator) # => false
[decorator].include?(user) # => true
{ user => 1 }[decorator] # => 1
{ decorator => 1 }[user] # => nilDelegator#== compares the wrapped object, but Struct#== checks the other operand’s class first, so equality only holds in one direction. Array#include? and Hash#[] inherit the asymmetry (the decorator forwards hash, which is why the first hash lookup succeeds).
Type checks see the wrapper, not the wrapped object:
decorator.is_a?(User) # => false
decorator.kind_of?(User) # => false
decorator.instance_of?(User) # => false
User === decorator # => false
decorator.class # => UserDecoratorcase/when and class patterns in case/in follow the same rule, since both use ===. A keys pattern does match, because deconstruct_keys is forwarded to the struct:
result =
case decorator
in User then :user
in { first_name: String => name } then name
end
result # => "John"The debugging trap: inspect and to_s are forwarded as well, so p decorator prints #<struct User first_name="John", last_name="Doe"> and nothing in the output says a decorator is involved. respond_to?(:first_name) and method(:first_name) do work, because Delegator implements respond_to_missing?. The rule of thumb: whenever identity matters, call __getobj__ and work with the original.
Building a Custom Delegator Subclass with __getobj__ and __setobj__
UserDecorator knows where to forward because SimpleDelegator inherits from Delegator, an abstract base class that expects its subclasses to implement __getobj__ and __setobj__. Providing __getobj__ is enough for a working delegator:
class MyDelegator < Delegator
attr_accessor :wrapped
alias_method :__getobj__, :wrapped
def initialize(obj)
@wrapped = obj
end
end
class UserDecorator < MyDelegator
def full_name
"#{first_name} #{last_name}"
end
end
UserDecorator.superclass # => MyDelegator
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.full_name # => "John Doe"SimpleDelegator itself is only slightly longer: its initialize calls __setobj__, which stores the object, and __getobj__ reads it back.
Generating a Wrapper Class with DelegateClass
The third option is the oddly named top-level method DelegateClass(klass), which generates and returns a delegator class for one specific class. You inherit from the result; the right-hand side of < can be any Ruby expression that returns a class:
class Logfile < DelegateClass(File)
MODE = File::WRONLY | File::CREAT | File::APPEND
def initialize(basename, logdir = "/var/log")
path = File.join(logdir, basename)
logfile = File.open(path, MODE, 0644)
# Delegator#initialize stores the File; every File method now works on a Logfile
super(logfile)
end
end
log = Logfile.new("app.log", "/tmp")
log.puts "Booted"
log.closeThe standard library builds Tempfile this way (Tempfile.superclass.superclass is Delegator), adding storage and cleanup rules on top of File.
The difference from SimpleDelegator is what happens when the class is created. DelegateClass(File) defines every public instance method of File on the generated class up front, so Logfile.superclass.public_instance_methods(false) includes :puts, :read, and the rest. Calls skip method_missing, which makes DelegateClass about 2.7 times faster per call than SimpleDelegator in the benchmark at the end of this guide. respond_to? and instance_methods also describe the real interface.
undefined method 'email' for an instance of UserDecorator (NoMethodError)
require "delegate"
User = Struct.new(:first_name, :last_name)
class UserDecorator < SimpleDelegator
def full_name
"#{first_name} #{last_name}"
end
end
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.email$ ruby /app/user_decorator.rb
/usr/local/lib/ruby/3.4.0/delegate.rb:91:in 'Delegator#method_missing': undefined method 'email' for an instance of UserDecorator (NoMethodError)
from /app/user_decorator.rb:12:in '<main>'
Cause: The wrapped object does not respond to the method, or the wrapped object is nil. UserDecorator.new(nil).first_name raises the exact same message, because Delegator#method_missing reports the wrapper, never the target. When the missing call is a bare identifier inside the subclass (as in "#{first_name} #{last_name}"), Ruby raises NameError instead: undefined local variable or method 'first_name' for an instance of UserDecorator (NameError).
Fix: Print decorated_user.__getobj__ first. If it is nil, fix the caller that built the decorator; if it is the right object, the method name is wrong or belongs to a different class.
Delegation with method_missing
SimpleDelegator and Rails’ delegate_missing_to are wrappers around one idea: catch every undefined method in method_missing, forward it if the target responds, and tell respond_to? the truth in respond_to_missing?. Written by hand, the pattern looks like this:
class Wrapper
def initialize(target)
@target = target
end
def method_missing(name, ...)
if @target.respond_to?(name)
@target.public_send(name, ...)
else
super
end
end
def respond_to_missing?(name, include_private = false)
@target.respond_to?(name, include_private) || super
end
end
wrapper = Wrapper.new(User.new("John", "Doe"))
wrapper.first_name # => "John"
wrapper.respond_to?(:first_name) # => trueThe metaprogramming guide walks through method_missing and respond_to_missing? in detail, and the method lookup post explains where BasicObject#method_missing sits in the lookup chain.
method_missing Pitfalls: respond_to_missing?, super, and Keyword Arguments
Four mistakes account for most bugs in hand-rolled delegation.
Omitting respond_to_missing?. The forwarded calls work, but wrapper.respond_to?(:first_name) returns false, and wrapper.method(:first_name) raises undefined method 'first_name' for class 'Wrapper' (NameError). Anything that introspects the object, from respond_to? guards to serializers, breaks.
Never calling super. Without the else branch, a typo such as wrapper.frist_name returns nil instead of raising, and the bug surfaces somewhere else.
Forwarding with *args, &block only. On Ruby 3, keyword arguments do not survive a splat-only forward:
class Greeter
def greet(name:)
"Hi, #{name}"
end
end
class SplatWrapper
def initialize(target)
@target = target
end
def method_missing(name, *args, &block)
@target.public_send(name, *args, &block)
end
def respond_to_missing?(name, include_private = false)
@target.respond_to?(name, include_private) || super
end
end
SplatWrapper.new(Greeter.new).greet(name: "Ada")$ ruby /app/splat_wrapper.rb
/app/splat_wrapper.rb:2:in 'greet': wrong number of arguments (given 1, expected 0; required keyword: name) (ArgumentError)
from /app/splat_wrapper.rb:13:in 'Kernel#public_send'
from /app/splat_wrapper.rb:13:in 'SplatWrapper#method_missing'
from /app/splat_wrapper.rb:21:in '<main>'
Forward with ... as in the Wrapper example (or mark the method with ruby2_keywords). Forwardable and SimpleDelegator both forward keyword arguments correctly.
A nil target. Wrapper.new(nil).first_name raises undefined method 'first_name' for an instance of Wrapper (NoMethodError): the message names the wrapper, exactly as with SimpleDelegator.
Rails delegate from Active Support
Rails’ delegate is not a class or a module: it is Module#delegate, a method Active Support adds to Module itself (in active_support/core_ext/module/delegation), which is why every class body in a Rails application can call it without an extend. There is no ActiveSupport::Delegate constant; referencing one raises uninitialized constant ActiveSupport::Delegate (NameError). The signature on Active Support 8.1.3.1 is delegate(*methods, to:, prefix:, allow_nil:, private:), and the Active Support core extensions guide documents it alongside the other Module extensions. Outside Rails, require "active_support/core_ext/module/delegation" loads it on its own.
Declaring Delegated Methods with delegate
List the methods, then name the target with to:. The target is usually a method, here the user reader:
User = Struct.new(:first_name, :last_name)
class UserDecorator
attr_reader :user
delegate :first_name, :last_name, to: :user
def initialize(user)
@user = user
end
def full_name
"#{first_name} #{last_name}"
end
end
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.full_name # => "John Doe"to: also accepts an instance variable, so the Printer from the Forwardable section needs no reader:
class Printer
delegate :render, to: :@formatter
def initialize(formatter)
@formatter = formatter
end
def print_page(text)
puts render(text)
end
end
Printer.new(Formatter.new).print_page(" Hello ")
# HelloA third form, to: :class, forwards to the class object; Rails rewrites that reserved name as self.class:
class Widget
def self.table_name
"widgets"
end
delegate :table_name, to: :class
end
Widget.new.table_name # => "widgets"delegate returns the array of method names it defined (delegate :first_name, to: :user returns [:first_name]), which is what makes private delegate :first_name, to: :user work.
The prefix: Option
prefix: true prepends the target’s name to each generated method; a symbol supplies a custom prefix instead:
class UserDecorator
delegate :first_name, :last_name, to: :user, prefix: true
delegate :first_name, to: :user, prefix: :account
end
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.user_first_name # => "John"
decorated_user.account_first_name # => "John"With to: :class, prefix: true, the generated name is class_table_name. An instance-variable target has no method name to derive a prefix from, so prefix: true raises there (see the prefix error); pass the prefix explicitly instead:
class Printer
delegate :render, :emphasize, to: :@formatter, prefix: :formatter
end
Printer.new(Formatter.new).formatter_emphasize("Hello") # => "*Hello*"The allow_nil: Option and the nil.respond_to? Gotcha
By default, a nil target raises ActiveSupport::DelegationError (its own section follows). With allow_nil: true, the generated method returns nil instead:
class UserDecorator
delegate :first_name, to: :user, allow_nil: true
end
UserDecorator.new(nil).first_name # => nilOne gotcha: the generated method first checks whether nil itself responds to the method, and calls it if so. nil responds to to_a and to_s, so those delegations return [] and "" rather than nil:
class UserDecorator
delegate :to_a, :to_s, to: :user, allow_nil: true
end
UserDecorator.new(nil).to_a # => []
UserDecorator.new(nil).to_s # => ""The private: Option
private: true makes the generated methods private, so the delegation stays an implementation detail rather than part of the wrapper’s interface:
class UserDecorator
attr_reader :user
delegate :first_name, to: :user, private: true
def initialize(user)
@user = user
end
def initial
"#{first_name[0]}."
end
end
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.initial # => "J."Calling decorated_user.first_name from outside now raises private method 'first_name' called for an instance of UserDecorator (NoMethodError). The older idiom private delegate :first_name, to: :user has the same effect.
Delegating Across Active Record Associations
The most common delegate in a Rails app forwards across an association:
class User < ApplicationRecord
belongs_to :organization
delegate :name, to: :organization, prefix: :organization
enduser.organization_name now reads user.organization.name, and the view never has to know about the association (verified on Active Record 8.1.3.1 with sqlite3 2.9.6).
belongs_to associations are required by default in a Rails 5+ application, so a saved User always has an organization. An unsaved record, or an association declared optional: true, can still be nil at call time, and then user.organization_name raises organization_name delegated to organization, but organization is nil (ActiveSupport::DelegationError). Add allow_nil: true when a missing organization is a legitimate state, and the call returns nil.
delegate_missing_to
delegate_missing_to is SimpleDelegator without the wrapper class: it defines method_missing and respond_to_missing? for you, forwarding every method the class does not define to the target.
class UserDecorator
attr_reader :user
delegate_missing_to :user
def initialize(user)
@user = user
end
def full_name
"#{first_name} #{last_name}"
end
end
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.full_name # => "John Doe"
decorated_user.respond_to?(:first_name) # => trueIts signature is delegate_missing_to(target, allow_nil: nil). A method the target lacks raises undefined method 'email' for an instance of UserDecorator (NoMethodError) from UserDecorator#method_missing; a nil target raises the same ActiveSupport::DelegationError as delegate (first_name delegated to user, but user is nil), and allow_nil: true turns that into nil.
first_name delegated to user, but user is nil (ActiveSupport::DelegationError)
require "active_support/core_ext/module/delegation"
User = Struct.new(:first_name, :last_name)
class UserDecorator
attr_reader :user
delegate :first_name, :last_name, to: :user
def initialize(user)
@user = user
end
end
decorated_user = UserDecorator.new(nil)
decorated_user.first_name$ ruby /app/user_decorator.rb
/app/user_decorator.rb:8:in 'UserDecorator#first_name': first_name delegated to user, but user is nil (ActiveSupport::DelegationError)
from /app/user_decorator.rb:16:in '<main>'
/app/user_decorator.rb:8:in 'UserDecorator#first_name': undefined method 'first_name' for nil (NoMethodError)
from /app/user_decorator.rb:16:in '<main>'
Cause: The to: target evaluated to nil when the delegated method ran. Rails rescues the resulting NoMethodError and re-raises it as ActiveSupport::DelegationError with the original as its cause, which is why an uncaught one prints two exceptions. Older posts and answers call it Module::DelegationError; that is the same constant (Module::DelegationError.equal?(ActiveSupport::DelegationError) is true), and it subclasses NoMethodError, so rescue NoMethodError catches it. The message always names the generated method and the target: an Active Record association with a prefix gives organization_name delegated to organization, but organization is nil, an instance-variable target gives render delegated to @formatter, but @formatter is nil, and delegate_missing_to raises the same text from UserDecorator#method_missing.
Fix: Make the target present: mark the association required, or guard the caller so it never builds the wrapper around nil. Use allow_nil: true only when nil is a legitimate state for that object, because it silences the message for every caller.
Delegation needs a target. Supply a keyword argument 'to' (ArgumentError)
require "active_support/core_ext/module/delegation"
class UserDecorator
delegate :first_name
end$ ruby /app/user_decorator.rb
/usr/local/bundle/gems/activesupport-8.1.3.1/lib/active_support/delegation.rb:23:in 'ActiveSupport::Delegation.generate': Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter). (ArgumentError)
raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)."
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
from /usr/local/bundle/gems/activesupport-8.1.3.1/lib/active_support/core_ext/module/delegation.rb:161:in 'Module#delegate'
from /app/user_decorator.rb:4:in '<class:UserDecorator>'
from /app/user_decorator.rb:3:in '<main>'
Cause: delegate was called without to:. Rails raises while the class body loads, so the whole file fails to load. In a Rails app, the error surfaces on boot or on the first request that autoloads the class.
Fix: Add the target: delegate :first_name, to: :user.
Can only automatically set the delegation prefix when delegating to a method. (ArgumentError)
require "active_support/core_ext/module/delegation"
class Printer
delegate :render, :emphasize, to: :@formatter, prefix: true
end$ ruby /app/printer.rb
/usr/local/bundle/gems/activesupport-8.1.3.1/lib/active_support/delegation.rb:27:in 'ActiveSupport::Delegation.generate': Can only automatically set the delegation prefix when delegating to a method. (ArgumentError)
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
from /usr/local/bundle/gems/activesupport-8.1.3.1/lib/active_support/core_ext/module/delegation.rb:161:in 'Module#delegate'
from /app/printer.rb:4:in '<class:Printer>'
from /app/printer.rb:3:in '<main>'
Cause: prefix: true derives the prefix from the target’s method name, and an instance-variable target such as :@formatter has none. Like the previous error, this one raises while the class loads.
Fix: Supply the prefix yourself with prefix: :formatter, or delegate to a reader method (attr_reader :formatter plus to: :formatter).
undefined local variable or method 'user' for an instance of UserDecorator (NameError)
require "active_support/core_ext/module/delegation"
User = Struct.new(:first_name, :last_name)
class UserDecorator
delegate :first_name, to: :user
def initialize(user)
@user = user
end
end
UserDecorator.new(User.new("John", "Doe")).first_name$ ruby /app/user_decorator.rb
/app/user_decorator.rb:6:in 'UserDecorator#first_name': undefined local variable or method 'user' for an instance of UserDecorator (NameError)
Did you mean? @user
from /app/user_decorator.rb:13:in '<main>'
Cause: to: :user names a method, and the class never defined one; the value lives only in @user. The generated method calls user, and Ruby’s did_you_mean suggests the instance variable.
Fix: Add attr_reader :user, or delegate to the variable directly with to: :@user.
Beyond the Standard Library
Rails’ delegate is by far the most-used delegation tool outside the standard library, and the previous section covers it. The one gem worth knowing beyond that implements the strict definition of delegation, running a method in the context of another object: Casting.
The Casting Gem: Delegation That Preserves self
Jim Gay’s Casting gem (casting 1.0.3 installs and runs on Ruby 3.4.10) delegates a method to an object while self stays the original receiver. The behavior lives in a module, and it has to be a module: binding a class-owned method to a Struct raises TypeError: bind argument must be an instance of UserDecorator. Given the module, UserDecorator.instance_method(:full_name).bind(user).call is the trick Casting wraps:
require "casting"
User = Struct.new(:first_name, :last_name)
module UserDecorator
def full_name
"#{first_name} #{last_name}"
end
end
user = User.new("John", "Doe")
user.extend(Casting::Client)
user.delegate(:full_name, UserDecorator) # => "John Doe"After user.delegate_missing_methods, Casting.delegating(user => UserDecorator) { user.full_name } adds the behavior for the block only; outside the block, user.full_name raises undefined method 'full_name' for #<User:0x…>. user.cast_as(UserDecorator) keeps it until user.uncast.
Comparison of Delegation Techniques
| Technique | Needs | How the methods exist | respond_to? | What is exposed | Per-call cost | Reach for it when |
|---|---|---|---|---|---|---|
| Explicit method | Nothing | You write each one | Yes | Only what you write | ~60 ns (baseline) | One to three methods, or the forwarding should be visible |
Forwardable | require "forwardable" and extend | Generated, one per listed name | Yes | Only the listed names, renamed if you like | ~170 ns (3x) | A curated list in plain Ruby |
DelegateClass(klass) | require "delegate" | Generated up front for every public method of klass | Yes | The whole public interface of klass | ~185 ns (3.2x) | A decorator over one known class |
SimpleDelegator | require "delegate" | method_missing at call time | Yes, via respond_to_missing? | Everything the wrapped object responds to | ~500 ns (8.5x) | Decorators and presenters over any object |
Hand-rolled method_missing | Nothing | method_missing at call time | Only if you write respond_to_missing? | Whatever your guard allows | ~260 ns (4.4x) | Custom rules for what forwards |
Rails delegate | Active Support | Generated, one per listed name | Yes | Only the listed names, with prefix:, allow_nil:, and private: | ~80 ns (1.4x) | Any Rails class |
delegate_missing_to | Active Support | method_missing at call time | Yes | Everything the target responds to | ~280 ns (4.8x) | A decorator inside Rails without a wrapper class |
The cost column comes from one benchmark-ips 2.15.1 run in a container on Ruby 3.4.10, with multipliers relative to the explicit call. Read the ordering as directional and the numbers as approximate. Every technique here stays under a microsecond per call; the difference only shows up in hot loops that forward millions of calls.
Choosing comes down to how much of the wrapped object you want to expose. Write the method yourself for one or two methods. Use Forwardable for a curated list in plain Ruby. Reach for SimpleDelegator or DelegateClass when a decorator should quack like the wrapped object, keeping the identity gotchas in mind. Inside Rails, delegate covers the curated case and delegate_missing_to the quack-like-everything case, with allow_nil: and prefix: thrown in.
Wrapping Up
Delegation in Ruby ranges from a one-line forwarding method to a Delegator that answers every message, and the right tool follows from how much of the wrapped object’s interface you want to expose and whether callers need to tell the two apart. If sharing behavior between classes is the real goal, mixins and modules are the composition alternative, and for the machinery under SimpleDelegator and delegate_missing_to, the metaprogramming guide goes further. Whichever you choose, the error strings in this guide are the ones you will meet the day a target turns out to be nil.
P.S. If you’d like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!
Frequently asked questions
- What is the difference between Forwardable and SimpleDelegator in Ruby?
- Forwardable generates one real method per name you list with def_delegator, so only those methods are exposed and respond_to? works without extra code. SimpleDelegator wraps an object and forwards every method you do not define through method_missing, which is more permissive and, per call, several times slower.
- How does allow_nil work with Rails delegate?
- With allow_nil: true, a delegated method returns nil instead of raising ActiveSupport::DelegationError when the target is nil. One exception: if nil itself responds to the method, Rails calls it, so delegating to_a or to_s with allow_nil returns an empty array or string, not nil.
- What does the Rails error delegated to user, but user is nil mean?
- It is ActiveSupport::DelegationError, a NoMethodError subclass raised when a method declared with delegate runs while its target method or variable returns nil. Fix the nil, for example by making the association required, or pass allow_nil: true if nil is a legitimate state for that object.
- Why does a SimpleDelegator object fail an is_a? check for the wrapped class?
- A SimpleDelegator is its own object with its own class; is_a?, kind_of?, instance_of?, and case/when compare against that class, not the wrapped one. Equality is one-directional too: decorator == user is true, but user == decorator is false. Call __getobj__ when you need the original object.
- Can I use prefix: true with delegate when the target is an instance variable?
- No. Rails raises ArgumentError, Can only automatically set the delegation prefix when delegating to a method, because it derives the prefix from a method name. Either delegate to a reader method such as to: :formatter, or supply the prefix explicitly with prefix: :formatter.
Published , Updated
Wondering what you can do next?
- Subscribe to our Ruby Magic newsletter and never miss an article again.
- Start monitoring your Ruby app with AppSignal.
- Share this article on social media

Jeff Morhous
Our guest author Jeff Morhous is a Software Engineer writing code and fixing bugs to help patients get the medications they need. These days he's focused on making web applications with Ruby on Rails, but in the past he's used Swift, Java, and Kotlin for iOS and Android development.
All articles by Jeff MorhousBecome our next author!
AppSignal monitors your apps
AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!


