paletteDecorator

Description

Decorator is a structural design pattern that allows you to add new behaviors to objects by wrapping them with decorator objects.

Problem

We want to create a User class in which we will store basic information, but we want to move all helper methods to another place to prevent creating too large a class.

Solution

First, we will create a User class that stores information about his name and birth date.

class User
  attr_reader :name, :birth_date

  def initialize(name:, birth_date:)
    @name = name
    @birth_date = birth_date
  end
end
Next, we would now like to extend our class with additional methods that return values ​​such as:
  • user's age
  • the day of the week he was born
  • next birthday date
  • days until the next birthday
We could add them to the user class, but we know that our class may grow even more in the future, so in this case we do not want to clutter it with helper methods.
Therefore, we want to move all responsibilities related to the user’s date of birth to a new class, which will be our decorator class.
The first step is to create a Decorator class that wraps the base class:

class UserDecorator
  attr_reader :user
  
  def initialize(user)
    @user = user
  end
end
Then we should delegate all public methods of the base class to the decorator class.

class UserDecorator
  # ...

  def name
    user.name
  end
  
  def birth_date
    user.birth_date
  end
end
At this point we are ready to extend our decorator with additional methods.

class UserDecorator
  # ...

  def age
    ((Time.now - birth_date) / 60 / 60 / 24 / 365).to_i
  end

  def birth_date_week_day
    birth_date.strftime("%A")
  end

  def next_birthday_date
    Time.new(Time.now.year, birth_date.month, birth_date.day)
  end

  def days_to_birthday
    ((next_birthday_date - Time.now) / 60 / 60 / 24).to_i
  end
end
Now we can easily use our Decorator and all the methods we have created.

user = User.new(name: 'John Doe', birth_date: Time.new(1999, 12, 31))
decorator = UserDecorator.new(user)

decorator.age                 # => 24
decorator.birth_date_week_day # => "Friday"
decorator.next_birthday_date  # => => 2024-12-31 00:00:00 +0100
decorator.days_to_birthday    # => 327
Thanks to the fact that we delegate all methods from the base class, we can use them in the same way as if we were using the User class.

decorator.name       # => 'John Doe'
decorator.birth_date # => 1999-12-31 00:00:00 +0100
If you would like to use a ready-made solution, there is a gem called draper: https://github.com/drapergem/draper that implements the decorator pattern in Ruby.