Skip to Content
Course content

38: Common Ruby Interview Questions on Metaprogramming

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 uses define_method to create a predicate method (ending in a question mark, e.g., can_edit?). This method should return true if the permission name exists in an internal @permissions array.
  • Implement method_missing to handle any method starting with check_. For example, check_admin should return a string: "Checking admin permission...".
  • Crucially, implement respond_to_missing? so that respond_to? correctly returns true for any check_ 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 by respond_to?.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.