paletteDecorator
Decorator is a structural design pattern that allows you to add new behaviors to objects by wrapping them with decorator objects.
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.
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:
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.