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
148: Method Missing vs respond_to_missing?
You've probably encountered "ghost methods" before—methods that aren't actually defined in the class but somehow still work when you call them. In Ruby, this magic happens via method_missing. It's a powerful tool, but it comes with a responsibility. If you tell Ruby how to handle a missing method, you also have to tell Ruby that the object actually responds to that method.
Creating a flexible settings object
Let's build a AppConfig class. I want to be able to pass a hash of settings into it and then access those settings as if they were methods. It's much cleaner to write config.api_key than config[:api_key] throughout a large codebase.
class AppConfig
def initialize(settings = {})
@settings = settings
end
def method_missing(method_name, *args, &block)
if @settings.key?(method_name)
@settings[method_name]
else
super
end
end
end
config = AppConfig.new(api_key: "secret_123", timeout: 30)
puts config.api_key # => "secret_123"
puts config.timeout # => 30
This works great for the happy path. I'm intercepting the call, checking my internal hash, and returning the value. If the key doesn't exist, I call super, which eventually triggers a NoMethodError, just as it should.
The lie we're telling the rest of the app
Here is where I usually trip up when I'm rushing a feature. I've implemented the logic, but I've forgotten that Ruby's introspection tools don't know about my method_missing logic. Watch what happens when we try to check if the object can handle a specific call:
config = AppConfig.new(api_key: "secret_123")
puts config.respond_to?(:api_key) # => false
Wait, what? I just called config.api_key and it worked perfectly! But respond_to? is lying to me. This is a problem because many gems and libraries use respond_to? to decide whether or not to call a method. If you're using a library that checks for capabilities before executing them, your AppConfig object will be seen as useless, even though it functions fine.
Aligning the truth with respond_to_missing?
To fix this, we use respond_to_missing?. This is a special hook that Ruby calls whenever respond_to? is invoked. Instead of overriding respond_to? directly (which is dangerous and old-school), we override respond_to_missing? to define the "truth" about our ghost methods.
class AppConfig
def initialize(settings = {})
@settings = settings
end
def method_missing(method_name, *args, &block)
if @settings.key?(method_name)
@settings[method_name]
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
@settings.key?(method_name) || super
end
end
config = AppConfig.new(api_key: "secret_123")
puts config.respond_to?(:api_key) # => true
puts config.respond_to?(:unknown) # => false
Notice that I added || super at the end of the respond_to_missing? method. This is crucial. Your class might inherit from another class that also uses method_missing, or it might have other inherited behaviors. By calling super, you ensure that you aren't accidentally silencing other parts of the Ruby object hierarchy.
The rule of thumb I follow is simple: If you find yourself typing def method_missing, your very next line of code should be def respond_to_missing?. They are two sides of the same coin.
📋 Practical Task
Building a Dynamic Translation Proxy
You are building a translation system where you want to call methods like t.welcome_message or t.error_not_found, and have the class look those keys up in a translation dictionary.
Create a class called Translator that:
- Takes a hash of translations in the
initializemethod. - Implements
method_missingso that calling a method returns the corresponding value from the hash. - Correctly implements
respond_to_missing?so thatrespond_to?returnstruefor keys that exist in the dictionary andfalsefor those that don't.
translator = Translator.new(welcome: "Hello!", goodbye: "Bye!")
puts translator.welcome # Should print "Hello!"
puts translator.respond_to?(:welcome) # Should print true
puts translator.respond_to?(:missing) # Should print false
There are no comments for now.