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

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_missing so 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: use super).
  • Implement respond_to_missing? so that respond_to? returns true for 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 NoMethodError
Rating
0 0

There are no comments for now.

to be the first to leave a comment.