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
12: Method Missing
I was working on a wrapper for a third-party API yesterday, and it reminded me of one of the most powerful—and dangerous—corners of Ruby: method_missing. Imagine we're building a class that wraps a hash of user data. We want to be able to call user.first_name or user.email without having to explicitly write every single getter method for every possible field the API might send us.
Wait, where did that method come from?
Let's start with a naive implementation. I'll just throw the data into a hash and try to call a method on it.
class UserProfile
def initialize(data)
@data = data
end
end
profile = UserProfile.new({ first_name: "Alice", email: "alice@example.com" })
puts profile.first_name
# NoMethodError: undefined method `first_name' for #<UserProfile:0x000...>
Well, that's expected. Ruby looks at the UserProfile class, sees no method named first_name, looks up the inheritance chain to Object and BasicObject, finds nothing, and gives up. But here is the secret: Ruby doesn't actually give up immediately. Before it throws that NoMethodError, it makes one last-ditch effort: it calls a method called method_missing.
Catching the fall
Since method_missing is just another method, I can override it. I can basically tell Ruby, "Hey, if you can't find the method I'm looking for, don't panic. Run this code instead."
class UserProfile
def initialize(data)
@data = data
end
def method_missing(method_name, *args, &block)
if @data.key?(method_name)
@data[method_name]
else
super
end
end
end
profile = UserProfile.new({ first_name: "Alice", email: "alice@example.com" })
puts profile.first_name # => Alice
puts profile.email # => alice@example.com
Now it works. When I call profile.first_name, Ruby realizes first_name isn't defined. It triggers method_missing, passing the symbol :first_name as the first argument. I check my hash, find the key, and return the value. It feels like magic, and for a lot of Ruby gems (like ActiveRecord), this is exactly how "ghost methods" are implemented.
The danger of the void
You might have noticed I called super in the else block. This is non-negotiable. If I omit that, I'm essentially telling Ruby that my object responds to every single method in existence, even ones that should definitely fail.
Look what happens if I remove super and call a method that isn't in my hash and isn't a standard Ruby method:
# Imagine 'super' is removed from the method_missing block...
profile.some_random_method_that_doesnt_exist
# => nil (because the method returned nothing, but didn't crash)
This is a debugging nightmare. You'll have typos in your code that don't trigger errors; they just silently return nil. Always call super so that Ruby can continue its normal error-handling process for things you actually didn't intend to handle.
Lying to the rest of the program
There's one more catch. I've made my object behave like it has a first_name method, but if I ask it if it has that method, it'll lie to me.
profile.respond_to?(:first_name) # => false
This is a problem for other libraries or parts of your app that check for capabilities before calling them. To fix this, Ruby provides a companion method: respond_to_missing?. If you override method_missing, you should almost always override this too.
class UserProfile
def initialize(data)
@data = data
end
def method_missing(method_name, *args, &block)
@data.key?(method_name) ? @data[method_name] : super
end
def respond_to_missing?(method_name, include_private = false)
@data.key?(method_name) || super
end
end
profile = UserProfile.new({ first_name: "Alice" })
puts profile.respond_to?(:first_name) # => true
puts profile.respond_to?(:something_else) # => false
Now the object is honest. It tells the world it can handle first_name, and it actually does. It's a clean, dynamic way to handle data without writing hundreds of boilerplate methods.
📋 Practical Task
Build a Dynamic Open-Settings Store
You need to create a SettingsStore class that allows a developer to set and get configuration values dynamically. Instead of using a hash syntax (like settings[:theme]), the developer should be able to call methods directly (like settings.theme).
Requirements:
- The class should be initialized with a hash of default settings.
- Implement
method_missingso that calling a method that matches a key in the settings hash returns the value of that setting. - Ensure that if a method is called that doesn't exist in the settings and isn't a standard Ruby method, it still raises a
NoMethodError(hint: usesuper). - Implement
respond_to_missing?so thatrespond_to?returnstruefor any key present in the settings hash.
Test your implementation with this snippet:
store = SettingsStore.new({ theme: "dark", language: "en", timeout: 30 })
puts store.theme # Should print "dark"
puts store.timeout # Should print 30
puts store.respond_to?(:language) # Should print true
puts store.respond_to?(:unknown) # Should print false
store.invalid_method # Should raise NoMethodErrorThere are no comments for now.