Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
96: The Decorator Pattern in Ruby
A few years ago, I was reviewing a pull request for a junior dev working on a massive e-commerce platform. He had added a method to the User model called display_name_with_membership_badge. It looked innocent enough, but when I scrolled through the user.rb file, I realized it was already 1,200 lines long. The model was doing everything: database queries, password hashing, and now, deciding which HTML string to return for a UI badge. He was stuck in that classic trap where the model becomes a "God Object" because that's the only place that has access to the data.
This is exactly where the Decorator pattern saves your sanity. Instead of polluting your business logic with presentation concerns, you wrap your object in a "decorator" that adds the extra behavior. In Ruby, we can do this elegantly without writing a mountain of boilerplate code.
Stop Cramming Presentation into Your Models
When you find yourself writing methods in a model that return formatted strings, HTML, or currency symbols, you're violating the Single Responsibility Principle. Your User model should care about who the user is, not how their name looks in a sidebar. But if you move that logic to a helper method, you end up with a disconnected set of functions that are hard to test and maintain.
The Decorator pattern allows you to create a specialized object that "decorates" the original one. It maintains the same interface as the original object but adds new methods. If the decorator doesn't know how to handle a method call, it simply passes that call along to the wrapped object. It's like a thin skin over your model.
Leveraging SimpleDelegator for Clean Wrappers
You could implement this by manually forwarding every method call, but that's a waste of time. Ruby provides a built-in class called SimpleDelegator in the standard library that handles the heavy lifting for you. I almost always use this when I need a quick, clean decorator.
require 'delegate'
class User
attr_reader :first_name, :last_name, :membership_level
def initialize(first_name, last_name, membership_level)
@first_name = first_name
@last_name = last_name
@membership_level = membership_level
end
end
class UserDecorator < SimpleDelegator
def full_name
"#{first_name} #{last_name}"
end
def membership_badge
case membership_level
when :gold then "[Gold Member] #{full_name}"
when :silver then "[Silver Member] #{full_name}"
else full_name
end
end
end
user = User.new("Jane", "Doe", :gold)
decorated_user = UserDecorator.new(user)
puts decorated_user.full_name # => Jane Doe
puts decorated_user.membership_badge # => [Gold Member] Jane Doe
puts decorated_user.first_name # => Jane (delegated to original User object)
Notice how decorated_user.first_name still works? Since UserDecorator inherits from SimpleDelegator, any method it doesn't explicitly define is automatically passed to the user instance we passed into the constructor. It's seamless.
Stacking Behavior Dynamically
One of the coolest things about this pattern is that decorators can be stacked. Since the decorator mimics the interface of the wrapped object, you can wrap a decorator in another decorator. I've used this in the past to apply different layers of formatting depending on whether a user is viewing a page in "Admin Mode" or "Guest Mode."
You might have a BaseUserDecorator for general formatting and an AdminUserDecorator that adds sensitive system IDs to the display. By stacking them, you don't have to create a massive inheritance tree of classes; you just compose the behavior you need at runtime. It keeps your code flexible and prevents that 1,200-line model file from ever happening in the first place.
📋 Practical Task
Build a Product Pricing Decorator
You are working on a store application. The Product model contains raw data (price, name, and a discount percentage), but the frontend needs specific formatting for different scenarios.
Your Task:
- Create a
Productclass withname,price(float), anddiscount_percentage(integer). - Create a
ProductDecoratorusingSimpleDelegator. - Implement a
formatted_pricemethod in the decorator that returns the price as a string with a currency symbol (e.g., "$19.99"). - Implement a
sale_pricemethod that calculates the price after the discount and returns it as a formatted string. - Implement a
price_displaymethod that returns thesale_priceif a discount exists, otherwise it returns theformatted_price.
Test your implementation by creating a product with a discount and one without, wrapping both in the decorator, and printing their price_display.
There are no comments for now.