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
38: Common Ruby Interview Questions on Metaprogramming
Look, when you get to the metaprogramming section of a Ruby interview, the interviewer isn't just checking if you know the syntax. They want to see if you understand the trade-offs. If you just start throwing method_missing at every problem, it's a red flag—it suggests you're using a sledgehammer to hang a picture frame. In a real production environment, "magic" is great until it's time to debug a stack trace at 3 AM.
The most common questions usually revolve around how to dynamically create methods and how to handle calls to methods that don't exist. To answer these well, you need a concrete mental model. Let's build a simple SchemaModel class that allows us to define attributes dynamically, similar to how ActiveRecord handles database columns.
Defining attributes on the fly
The first question you'll likely face is: "When should you use define_method versus method_missing?" My rule of thumb is simple: if you know the names of the methods you need at the time the class is loaded or initialized, use define_method. It's faster because Ruby doesn't have to fail a method lookup before hitting the fallback.
class SchemaModel
def self.attribute(name)
# We define a getter
define_method(name) do
instance_variable_get("@#{name}")
end
# And a setter
define_method("#{name}=") do |value|
instance_variable_set("@#{name}", value)
end
end
end
class User < SchemaModel
attribute :first_name
attribute :email
end
user = User.new
user.first_name = "Matz"
puts user.first_name # => "Matz"
I'm using define_method here because we're explicitly telling the class which attributes exist. This is clean, and it plays nicely with Ruby's method lookup cache.
Adding flexibility with method_missing
Now, what if we want to support "virtual" attributes? Say we want our model to handle any method that starts with find_by_ without explicitly defining every single column in the database. This is where method_missing comes in. This is the "catch-all" of the Ruby world.
class SchemaModel
# ... previous code ...
def method_missing(method_name, *args, &block)
if method_name.to_s.start_with?("find_by_")
field = method_name.to_s.sub("find_by_", "")
puts "Searching database for #{field} with value #{args.first}..."
# In a real app, this would be a DB query
return "Result for #{field}"
end
super # Always call super if you don't handle the method!
end
end
user = User.new
puts user.find_by_email("test@example.com") # => "Searching database... Result for email"
Why my code broke the Ruby contract
Here is where I usually trip up when I'm rushing through a demo, and it's a mistake that interviewers love to catch. If I run user.respond_to?(:find_by_email) right now, Ruby will return false. Why? Because method_missing doesn't actually define a method; it just handles the failure of finding one.
This is a huge problem. Many libraries (and other developers) check respond_to? before calling a method to avoid raising a NoMethodError. By implementing method_missing without its partner, I've lied to the rest of the program about what this object can actually do.
To fix this, I have to override respond_to_missing?. This is the modern way (introduced in Ruby 1.9) to tell Ruby, "Yes, I can handle this, even though you can't find the method in the lookup table."
class SchemaModel
# ... previous code ...
def respond_to_missing?(method_name, include_private = false)
method_name.to_s.start_with?("find_by_") || super
end
def method_missing(method_name, *args, &block)
if respond_to_missing?(method_name)
field = method_name.to_s.sub("find_by_", "")
"Result for #{field}"
else
super
end
end
end
user = User.new
puts user.respond_to?(:find_by_email) # => true (Fixed!)
Tying it all together for the interviewer
When you're explaining this in an interview, don't just show the code. Narrate the trade-offs. Tell them: "I used define_method for the attributes because it's more performant and explicit. I used method_missing for the searchers to allow for an infinite variety of fields. And I implemented respond_to_missing? to ensure the object maintains a consistent API contract."
That answer shows you aren't just mimicking patterns you saw in a Rails source file—it shows you understand the underlying mechanics of the Ruby object model.
📋 Practical Task
Implementing a Dynamic Permission Checker
Build a class called PermissionManager that uses metaprogramming to handle dynamic permission checks. Your implementation should meet the following requirements:
- Create a class method
define_permission(name)that usesdefine_methodto create a predicate method (ending in a question mark, e.g.,can_edit?). This method should returntrueif the permission name exists in an internal@permissionsarray. - Implement
method_missingto handle any method starting withcheck_. For example,check_adminshould return a string:"Checking admin permission...". - Crucially, implement
respond_to_missing?so thatrespond_to?correctly returnstruefor anycheck_method. - Test your class by creating an instance, adding a few permissions, and verifying that both the defined permission methods and the dynamic
check_methods work and are recognized byrespond_to?.
There are no comments for now.