Utilize Metaprogramming in Ruby with method_missing to Create Methods on the Fly
This will be an interesting lesson, we are going to learn how to create code that will write code inside a Ruby program by leveraging the metaprogramming construct of method_missing.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

This will be an interesting lesson, we are going to learn how to create code that will write code inside a Ruby program by leveraging the metaprogramming construct of method_missing. Though it's an advanced Ruby concept, it's fun to learn and I think it'll be handy for you. So, let's get started.

Imagine we have a database called Author and in it, we have different attributes such as first_name, last_name and genre. Now, what do we do if we want to create dynamic methods on the fly? For example, if we create a new author in the database, we want custom methods to print out each of the attributes. At the same time, we don't want to hard code a bunch of methods, we want them to get generated dynamically. Essentially, we want a metaprogramming method that will write code dynamically on the fly based on the arguments sent to it.

Let's see how to do that.

require 'ostruct'

class Author
  attr_accessor :first_name, :last_name, :genre

  def author
    OpenStruct.new(first_name: first_name, last_name: last_name, genre: genre)
  end

  def method_missing(method_name, *arguments, &block)
    if method_name.to_s =~ /author_(.*)/
      author.send($1, *arguments, &block)
    else
      super
    end
  end
end

author = Author.new
author.first_name = "Cal"
author.last_name = "Newpot"
author.genre = "Computer Science"

Let's walk through the code line by line.

In the first line, we are including a library that'll give us access to some methods. In this code, we'll be using it as a data structure to mimic a database, since creating a database and populating it would take up too much time.

Next, we are creating a class called Author and we are defining attributes for it. In the next line, we are creating a method called author, and inside it we are creating an OpenStruct object with values for each attribute. Essentially this will function like a database.

Next, we are using a key method called method_missing that is built into Ruby. In this sense, we are not creating a new method, but we are overriding an existing method. It takes three arguments, namely:

  1. A method name
  2. An array of arguments
  3. A code block

Inside this method, we are placing a conditional that checks if the method name that was passed starts with author_, and if it does, we are calling the author method and sending arguments to it. The argument $1 grabs the first element in the argument array, *arguments will pass the remaining arguments, and &block will pass the block of code.

If this conditional fails, we are calling super, as we want the code to just call the parent class. Since we haven't inherited from any other class, Ruby will look for this method in BasicObject class, and will do nothing with it.

Why do we need to call super?

Let's say we have a method that does not start with author_. In that case, we are telling Ruby that we don't have any code for such a method. In other words, we want Ruby to generate methods only for those values that start with author_, and not for other methods.

To check if this code works, let's instantiate Author. We are creating a new variable called author and we are setting attributes for this object.

Finally, let's test it out. Before that, let's print out our author's first name to know if the program is working fine. So, we add this piece of code to establish a base case:

p author.first_name

The output is:

Cal

and that's right!

Now, let's see what happens if we change it to

p author.author_genre

The output is Computer Science.

So, we have successfully implemented a full meta-programming module for our Author class.

If you see, we don't have a method called author_genre, yet it returned the right value because this method was created on the fly by method_missing. This is an important method to know as it can give you quite a bit of flexibility while building out Ruby programs.

One important thing I want to show you is the respond_to? method. This method comes by default with all functioning methods, and it checks if a particular method exists in the code.

For example:

p author.respond_to?(:inspect)

will return the value true because inspect comes by default for all objects. On the other hand, if we say:

p author.respond_to?(:author_genre)

It returns the value false because this method is not present in the code.

This is a potential problem, because many programmers will put respond_to? in a conditional to check if a particular method exists before executing the remaining code, just to ensure that the program works. In the next guide, we'll see how to overcome this drawback.

Resources