Thought Schema

Programming the 20-something dream

Method_Missing: Gone Girl Your Code

Yes, Gone Girl is a verb. To avoid giving anything away, take it to mean to disappear, go absolutely mental, and return to an altered reality. Ruby has something similar: method_missing.

Gone Girl

Wonder how ActiveRecord creates those find methods on the fly? Method_messing. In fact, method_missing is a pillar of metaprogramming and is instrumental in the creation of DSLs.

When a method is called, Ruby looks for the method in the instance methods, class methods, superclass methods, mixins, etc. Method_missing is the last resort.

This means right before Ruby throws a NoMethod error, you can catch it with method_missing.

To see how it works, let’s see how Active Record’s find_by* methods are created:

1
2
3
4
5
6
7
8
class ActiveRecord::Base
  def method_missing(method_name, *args, &block) #1
    if method_name.to_s =~ /^find_by_(.+)$/ #2
      run_find_by_method($1, *args, &block) #3
    else
      super #4
    end
  end
  1. Method_missing takes three parameters: the method name, splatted arguments, and then a block to run.
  2. ActiveRecord is converting the method name into a string and then using Regex to match a find_by* pattern.
  3. This is the black box where the find_by* method is actually defined. When the method is defined, method_missing will no longer be called for that particular method name.
  4. Super allows Ruby to continue doing its thing if the match did not happen.

Try it yourself:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def method_missing(method_name, *arguments, &block)
  if method_name.to_s =~ /pattern_to_match/

    #This defines a class method for future usage.
    self.class.send (:define_method, method_name) do
      # Your magic
    end

    #This runs the newly minted method on your instance.
    self.send(method_name)
  else
    super
  end
end

#To ensure continuity in how Ruby treats methods, 
#you override the respond_to method Ruby has 
#with a match to your new method.
def respond_to?(method_name, include_private = false)
  method_name.to_s =~ /^what_is_[\w]+/ ? true : super
end

Go forth and catch those missing methods. Do not, however, Gone Girl.