In this post, I would like to discuss one use case, out of several use cases, for Ruby’s metaprogramming that I found very helpful while working on building a command line interface application (CLI) with external data for my first portfolio project at Flatiron school. I am referring to the use of mass assignment for the Initialize method. One of the requirements of my project was that my CLI should make only one call to an external source, in my case I used the National Park Service API that returned a large data set in the format of an array of nested hashes. During the early stages of development of my project I found myself ‘mining’ the array of nested hashes for the data I intended to use with my CLI and then compiling a list of variable names for which I knew I was going to have to create at least an instance variable for each of them and, using the attr_accessor macro, a setter and getter method for each them. This was going to be a sizable task given the large amount of interesting data I could see in what was being returned from the API.
Ruby’s metaprogramming offers a lot of functionality. You could write every bit of code from scratch yourself if you wanted to, but doing that is very time consuming and quite frankly inefficient. If you have read this far, it would be safe to assume you are familiar with the fact that methods can be defined to take in multiple arguments. For example:
On line four of the code snippet above, our initialize method is being passed four keyword arguments. Note: Keyword arguments are a special way of passing arguments into a method, pairing a key that functions as the argument name, with its value.
One of the benefits associated with using keyword arguments is the ability to use something called mass assignment to create new instances of the class Park, or in other words, instantiate new ‘Park’ objects. With the initialize method in the example above defined to accept keyword arguments I realized that I could use key-value pairs from the array of nested hashes in my API response and assign them to a variable, then I could pass that variable to my initialize method. One problem persisted, not all of my parks contained a description, in other words not all the new instances of the ‘Park’ objects were going to require the same number of variables, so I needed to find a way to abstract away the Park class’ dependency on a specific number of attributes. Let’s take a look at the code below:
I started by defining the initialize method to take in an ‘attributes’ variable which had been assigned a hash with the parks’ data. Then, I iterated over each key/value pair in the attributes hash. I called the class method attr_accessor on the Park class itself (on line four) and passed the key to dynamically add both a getter and a setter. Then I used the .send method which calls the method’s name that is the key’s name, with an argument of the value, and et viola! I had an initialize method that could easily adapt to a different number of attributes.
The above code snippet is considered metaprogramming.