If your project includes
ActiveSupport, and every Rails project does, you have a more clean and easy way to implement the delegation pattern: the
Module#delegate extension. It provides a delegate module you can use in your class or in your modules to delegate a specific method to an associate object.
#delegate provides a delegate class method to easily expose contained objects’ public methods as your own.
delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil)
:to - Specifies the target object name as a symbol or string
:prefix - Prefixes the new method with the target name or a custom prefix
:allow_nil - If set to true, prevents a Module::DelegationError from being raised
:private - If set to true, changes method visibility to private
The macro receives one or more method names (specified as symbols or strings) and the name of the target object via the
:to option (also a symbol or string).
Delegation is particularly useful with Active Record associations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Greeter < ActiveRecord::Base def hello 'hello' end def goodbye 'goodbye' end end class Foo < ActiveRecord::Base belongs_to :greeter delegate :hello, to: :greeter end Foo.new.hello Foo.new.goodbye
Multiple delegates to the same target are allowed:
1 2 3 4 5 6 class Foo < ActiveRecord::Base belongs_to :greeter delegate :hello, :goodbye, to: :greeter end Foo.new.goodbye
Methods can be delegated to instance variables, class variables, or constants by providing them as a symbols:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Foo CONSTANT_ARRAY = [ 0, 1, 2, 3] @@class_array = [ 4, 5, 6, 7] def initialize @instance_array = [ 8, 9, 10, 11] end delegate :sum, to: :CONSTANT_ARRAY delegate :min, to: : @@class_array delegate :max, to: : @instance_array end Foo.new.sum Foo.new.min Foo.new.max
It’s also possible to delegate a method to the class by using :class:
1 2 3 4 5 6 7 8 9 class Foo def self.hello "world" end delegate :hello, to: :class end Foo.new.hello
Delegates can optionally be prefixed using the
:prefix option. If the value is true, the delegate methods are prefixed with the name of the object being delegated to.
1 2 3 4 5 6 7 8 9 10 Person = Struct.new( :name, :address) class Invoice < Struct.new( :client) delegate :name, :address, to: :client, prefix: true end john_doe = Person.new( 'John Doe', 'Vimmersvej 13') invoice = Invoice.new(john_doe) invoice.client_name invoice.client_address
It is also possible to supply a custom prefix.
1 2 3 4 5 6 7 class Invoice < Struct.new( :client) delegate :name, :address, to: :client, prefix: :customer end invoice = Invoice.new(john_doe) invoice.customer_name invoice.customer_address
The delegated methods are public by default. Pass private: true to change that.
1 2 3 4 5 6 7 8 9 10 11 12 13 class User < ActiveRecord::Base has_one :profile delegate :first_name, to: :profile delegate :date_of_birth, to: :profile, private: true def age Date.today.year - date_of_birth.year end end User.new.first_name User.new.date_of_birth User.new.age
If the target is nil and does not respond to the delegated method a
Module::DelegationError is raised. If you wish to instead return
nil, use the
1 2 3 4 5 6 7 class User < ActiveRecord::Base has_one :profile delegate :age, to: :profile end User.new.age
But if not having a profile yet is fine and should not be an error condition:
1 2 3 4 5 6 class User < ActiveRecord::Base has_one :profile delegate :age, to: :profile, allow_nil: true end User.new.age
Note that if the target is not nil then the call is attempted regardless of the
:allow_nil option, and thus an exception is still raised if said object does not respond to the method:
1 2 3 4 5 6 7 8 9 class Foo def initialize( bar) @bar = bar end delegate :name, to: : @bar, allow_nil: true end Foo.new( "Bar").name
The target method must be public, otherwise it will raise NoMethodError.
Module | Ruby on Rails 6.1.4 Class - https://api.rubyonrails.org/classes/Module.html#method-i-delegate
Active Support Core Extensions — Ruby on Rails Guides - https://guides.rubyonrails.org/active_support_core_extensions.html#method-delegation
Ruby’s Hidden Gems: Delegator and Forwardable | AppSignal Blog - https://blog.appsignal.com/2019/04/30/ruby-magic-hidden-gems-delegator-forwardable.html
Understanding delegate in Ruby on Rails | by Meraj Molla | ITNEXT - https://itnext.io/understanding-delegate-in-ruby-on-rails-i-wish-i-knew-before-5edd341bad47
Understanding Ruby and Rails: Delegate — Simone Carletti - https://simonecarletti.com/blog/2009/12/inside-ruby-on-rails-delegate/
class SimpleDelegator - Documentation for Ruby 3.0.0 - https://docs.ruby-lang.org/en/3.0.0/SimpleDelegator.html
Module: Forwardable (Ruby 3.0.0) - https://ruby-doc.org/stdlib-3.0.0/libdoc/forwardable/rdoc/Forwardable.html
saturnflyer/casting: Delegate methods in Ruby and preserve self. Add behaviors to your objects without altering their superclass hierarchy. - https://github.com/saturnflyer/casting
How To Delegate Methods in Ruby & Ruby on Rails - RubyGuides - https://www.rubyguides.com/2018/10/delegate-methods-in-ruby/