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)
Options
: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
classGreeter < ActiveRecord::Base defhello 'hello' end
defgoodbye 'goodbye' end end
classFoo < ActiveRecord::Base belongs_to :greeter delegate :hello, to::greeter end
It’s also possible to delegate a method to the class by using :class:
1 2 3 4 5 6 7 8 9
classFoo defself.hello "world" end
delegate :hello, to::class end
Foo.new.hello # => "world"
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)
classInvoice < Struct.new(:client) delegate :name, :address, to::client, prefix:true end
defage Date.today.year - date_of_birth.year end end
User.new.first_name # => "Tomas" User.new.date_of_birth # => NoMethodError: private method `date_of_birth' called for #<User:0x00000008221340> User.new.age # => 2
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 :allow_nil option.
1 2 3 4 5 6 7
classUser < ActiveRecord::Base has_one :profile delegate :age, to::profile end
User.new.age # => Module::DelegationError: User#age delegated to profile.age, but profile is nil
But if not having a profile yet is fine and should not be an error condition:
1 2 3 4 5 6
classUser < ActiveRecord::Base has_one :profile delegate :age, to::profile, allow_nil:true end
User.new.age # nil
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: