- Read Tutorial
- Watch Guide Video
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
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:
- A method name
- An array of arguments
- 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
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:
The output is:
and that's right!
Now, let's see what happens if we change it to
The output is
So, we have successfully implemented a full meta-programming module for our
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.
will return the value
inspect comes by default for all objects. On the other hand, if we say:
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.