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
15: Inheritance and Polymorphism
You've likely reached a point in your project where you're starting to see patterns. You have a User class, an Admin class, and a Moderator class. They all have names, emails, and passwords, but they do slightly different things. Your first instinct is probably to copy-paste the common methods into all three. Don't do that. It's a maintenance nightmare.
Let's look at a piece of code I reviewed recently. The developer was building a payment system and tried to use inheritance, but they hit a wall that almost every Rubyist hits early on.
class Payment
def process(amount)
puts "Logging transaction of $#{amount} to the database..."
# Imagine complex logging logic here
end
end
class CreditCardPayment < Payment
def process(amount)
puts "Charging $#{amount} to Credit Card."
end
end
class PayPalPayment < Payment
def process(amount)
puts "Redirecting to PayPal for $#{amount}."
end
end
# Testing the code
payments = [CreditCardPayment.new, PayPalPayment.new]
payments.each { |p| p.process(100) }
The disappearing log entries
At first glance, this looks fine. It's clean, and it works. But if you run this, you'll notice something is missing: the "Logging transaction..." message from the Payment class never appears. The developer was confused because they inherited from Payment, so they assumed the base logic would just "happen" automatically.
Here is the reality: when you define a method in a subclass with the exact same name as a method in the parent class, you aren't adding to the method—you are completely replacing it. In Ruby, this is called method overriding. The CreditCardPayment#process method effectively nuked the Payment#process method for that object.
Restoring the chain with super
To fix this, we need a way to tell Ruby, "Do the stuff in the subclass, but also do the stuff the parent class knows how to do." That's exactly what the super keyword is for.
class Payment
def process(amount)
puts "Logging transaction of $#{amount} to the database..."
end
end
class CreditCardPayment < Payment
def process(amount)
super # This calls the process method of the parent (Payment)
puts "Charging $#{amount} to Credit Card."
end
end
class PayPalPayment < Payment
def process(amount)
super
puts "Redirecting to PayPal for $#{amount}."
end
end
Now, when you call process on a CreditCardPayment object, Ruby jumps to the subclass method, sees super, jumps up to the parent class to run the logging logic, and then comes back down to finish the credit card specific logic. I usually put super at the top if the parent handles setup or logging, but you can put it at the bottom if the parent handles the final cleanup.
Handling variety through polymorphism
Now that we have inheritance working, we can unlock Polymorphism. Don't let the Greek name intimidate you; it just means "many shapes." In practice, it means we can treat different objects as if they are the same type, as long as they respond to the same method.
Look back at how we iterated through the payments array: payments.each { |p| p.process(100) }. The loop doesn't know if p is a CreditCardPayment or a PayPalPayment. It only knows that p is some kind of Payment and that it has a process method.
This is the "secret sauce" of scalable software. If you decide to add CryptoPayment or ApplePayPayment next week, you don't have to change the loop that processes payments. You just create the new class, inherit from Payment, and implement process. The rest of your system just works.
The trap of the fragile base class
A word of caution from someone who has broken too many production apps: be careful how much logic you put in the parent class. When you put a method in the base class, every single subclass inherits it. If you change a method in Payment to fix a bug for Credit Cards, you might accidentally break PayPal.
If you find yourself writing if self.is_a?(CreditCardPayment) inside the parent class, stop immediately. You're fighting against polymorphism. Instead, move that specific logic into the subclass and let the parent class stay generic.
📋 Practical Task
Build a Multi-Channel Notification System
You need to build a system that sends notifications to users via different channels. Some users prefer Email, some prefer SMS, and some prefer Push notifications.
- Create a base class called
Notifierwith a methodsend_notification(message)that prints "Preparing system for transmission...". - Create three subclasses:
EmailNotifier,SMSNotifier, andPushNotifier. - Each subclass should override
send_notification. It must callsuperto ensure the preparation message is printed, and then print its own specific message (e.g., "Sending Email: [message]"). - Create an array containing one instance of each notifier.
- Iterate through the array and send the message "Your order has shipped!" using each notifier.
There are no comments for now.