How to Delegate Methods in Ruby
Tags: ruby
Published: 2023-07-26 11:14:08

Delegation in programming refers to handing off a task from one part of a program to another. It's an essential technique in object-oriented programming, empowering clean, maintainable code by ensuring that each object or method is responsible for a specific task or behavior.

Understanding and using delegation is key to mastering Ruby and other object-oriented languages. Delegation promotes separation of concerns, making your code more modular and easier to understand, test, and refactor.

In this article, we'll dive into three ways to achieve delegation in Ruby: using explicit delegation, the `Forwardable` module, and `ActiveSupport::Delegate` (for Rails).

Let's get started!

## Delegation in Ruby

Let's begin by discussing explicit delegation — calling a method within a method. Then we'll explore the built-in `Forwardable` module and how it can streamline delegation. Lastly, we'll cover `ActiveSupport::Delegate`, a Rails-specific delegation tool with some handy features.

### Explicit Delegation

In its simplest form, delegation can be achieved by explicitly calling a method within another method. This approach is often employed when the delegated method belongs to a separate object or when the delegation is simple enough not to warrant the use of more advanced techniques.

Implementing explicit delegation in Ruby is straightforward. Let's take an example of a `Printer` class that uses an HP object to print text:

```ruby
class HP
  def print(text)
    # Actual code to print
  end
end

class Printer
  def initialize(printer_model)
    @printer_model = printer_model
  end

  def print(text)
    @printer_model.print(text)
  end
end
```

In the example above, the print method of the `Printer` class explicitly delegates the formatting task to the `HP` class by calling its format method. This delegation outsources the printing responsibility to another class in a simple and readable way.

Explicit delegation is common in real-world applications, as it helps promote the separation of concerns and maintainable code. For instance, you might find this technique used in applications that require communication between different services, where one service calls another to perform a specific task.

Additionally, explicit delegation can be useful when building adapters or wrappers around third-party libraries, as it allows you to isolate the interaction with the external code.

In summary, explicit delegation is a straightforward and effective way to delegate tasks between objects in Ruby. By calling a method within another method, you can easily delegate responsibilities, making your code more maintainable and promoting the separation of concerns.

### Ruby's Forwardable Module

The [Forwardable module](https://ruby-doc.org/3.2.0/stdlibs/forwardable/Forwardable.html) is a built-in Ruby library that provides a more streamlined and flexible approach to delegate methods compared to explicit delegation. By including the Forwardable module in your class, you gain access to methods like `def_delegator` and `def_delegators`, making delegation a breeze.

To get started with the Forwardable module, include it in your class and use the `def_delegator` and `def_delegators` methods to delegate one or multiple methods, respectively. Let's revisit our Printer example with a new `Formatter` class and implement the Forwardable module:

```ruby
require 'forwardable'

class Formatter
  def format(text)
    # Perform formatting and return the formatted text
  end
end

class Printer
  extend Forwardable

  def_delegator :@formatter, :format

  def initialize(formatter)
    @formatter = formatter
  end

  def print(text)
    formatted_text = format(text)
    puts formatted_text
  end
end
```

In the example above, we've used `def_delegator` to delegate the format method from the Formatter class. If you need to delegate multiple methods at once, you can use `def_delegators`. For example, if the `Formatter` class had additional methods such as `capitalize`, you could delegate them like this:

```ruby
class Printer
  extend Forwardable

  def_delegators :@formatter, :format, :capitalize

  # rest of code
end
```

When using the Forwardable module, there are a few important potential drawbacks to consider:

- First, ensure that the target object is initialized before using delegation. Otherwise, you may encounter errors.
- Second, the overuse of delegation can lead to potential confusion in code readability, as it may become unclear which methods belong to the class and which are delegated.
- Finally, there may be a slight performance overhead when using Forwardable compared to direct method calls. However, this overhead is generally negligible in most applications.

For more in-depth information about the Forwardable module, see the [Forwardable documentation](https://ruby-doc.org/3.2.0/stdlibs/forwardable/Forwardable.html).

### `ActiveSupport::Delegate` for Rails Applications

[`ActiveSupport::Delegate`](http://api.rubyonrails.org/classes/Module.html#method-i-delegate) is a delegation utility provided by Rails, offering a concise syntax for delegating methods to associated objects. While it shares similarities with Ruby's built-in Forwardable module, `ActiveSupport::Delegate` offers additional options and features tailored for Rails applications.

To use `ActiveSupport::Delegate`, simply call the delegate method in your class and specify the method(s) to be delegated along with the target object. Let's revisit the Printer and Formatter example and implement delegation using `ActiveSupport::Delegate`:

```ruby
class Formatter
  def format(text)
    # Perform formatting and return the formatted text
  end
end

class Printer
  delegate :format, to: :@formatter

  def initialize(formatter)
    @formatter = formatter
  end

  def print(text)
    formatted_text = format(text)
    puts formatted_text
  end
end
```

`ActiveSupport::Delegate` offers additional options and features that can be useful in various scenarios. For instance, you can use the `:prefix` option to prepend a prefix to the delegated method. If you'd like to delegate multiple methods at once, simply list them before the `to:` option:

```ruby
class Printer
  delegate :format, :bold, :italic, to: :@formatter, prefix: true

  # ...
end
```

This generates methods like `formatter_format`, `formatter_bold`, and `formatter_italic` in the `Printer` class, delegating to the respective methods in the `Formatter` class.

`ActiveSupport::Delegate` is commonly used in real-world Rails projects to delegate methods between associated models or objects. For example, if you have a `User` model that `belongs_to` an organization, you could delegate the name method from the `organization` model to the `User` model like this:

```ruby
class User < ApplicationRecord
  belongs_to :organization
  delegate :name, to: :organization, prefix: :organization
end
```

This allows you to access the organization's name through a user object: `user.organization_name`.

In conclusion, `ActiveSupport::Delegate` is a powerful tool for Rails applications that enables concise and expressive delegation of methods. By leveraging its additional options and features, you can create maintainable and well-organized code that adheres to the principles of object-oriented programming in the context of Rails projects.

For more in-depth information about `ActiveSupport::Delegate` and all the available options, see the [Rails ActiveSupport documentation](http://api.rubyonrails.org/classes/Module.html#method-i-delegate).

## Comparison of Delegation Techniques

Explicit delegation is the most straightforward approach to delegation, requiring manually defined methods that call upon the desired methods from the delegated object. Although this technique is simple and doesn't require any external libraries, it may be too verbose when delegating a growing number of methods.

The Forwardable module, built into Ruby, offers a more streamlined way to delegate methods. By using the `def_delegator` and `def_delegators` methods, you can delegate one or multiple methods concisely.

`ActiveSupport::Delegate`, part of Rails' ActiveSupport library, provides a declarative way to handle method delegation. With a clean syntax and additional options like prefixing, it's an expressive tool in a Rails context. Compared to explicit delegation, `ActiveSupport::Delegate` is more expressive and less verbose, particularly when delegating multiple methods. However, it requires Rails, so it's not suitable for non-Rails Ruby projects.

Choosing a delegation technique depends on your specific needs and application. While explicit delegation offers simplicity and clarity, the Forwardable module provides a more streamlined approach, and `ActiveSupport::Delegate` offers a powerful, Rails-specific solution. Understanding these options lets you choose the technique that best fits your project and coding style.

## Wrapping Up

From explicit delegation to using the built-in Forwardable module or the Rails-specific `ActiveSupport::Delegate`, each technique offers its unique benefits and potential drawbacks. The technique you should choose depends on your specific context, the nature of your project, and your personal coding style.

Understanding these techniques not only broadens your Ruby programming skills but also equips you to write more modular, readable, and maintainable code. With this knowledge, you are better prepared to tackle complex problems, with eloquent design.

Happy delegating!

**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](https://blog.appsignal.com/ruby-magic)!**