My Goal with Understanding Metaprogramming in Ruby

You want to enjoy life, don’t you? If you get your job done quickly and your job is fun, that’s good isn’t it? That’s the purpose of life, partly. Your life is better.” — Matz, The Philosophy of Ruby

@ Fuu J — Unsplash

So Inspiring! Anyways, that was how Matz convinced me to start learning Ruby.

And that was important for me specially after some failing attempts at coding. Yup this was me:

Ever since I started learning Ruby my thought was how I can I make things easier for me, and by doing so for every other programmer around myself.

My first serious attempt in trying to see if I could apply this concept had to do when I decided to create a Ruby Gem that will read the Coingecko’s website API.

So my goal was to offer a Ruby wrap-around that in the Matz spirit, will make things life easier to any programmer that wishes to include Coingecko’s data into their own program. So this gem would allow oneself to get real-time market data from Coingecko, for any coin, just like this:

Just fast forward a couple hours into the project:

My code was getting ugly and I was struggling as to how to approach creating coin objects that had dozens or more of properties.

And so I’m back to my old habits

Take a deep breath….

I had saved a tiny piece of code (more like a screenshot of code) in my notes from a video. It read or looked something like this:

Then scramble through my notes and behold then Metaprogramming:

Metaprogramming is the art of writing code that writes code for us

Great! That’s what I want. I want to be able to create objects with attributes for all the API JSON that I’m passing to my method. So I want to have a method that writes that code for me and makes life easier for anyone else using the code in the future.

So because I’m lazy I want to be able reuse to the least the spirit of the above piece of code that will help me accomplish creating my coin objects with the use of Metaprogramming.

Nice, where do we start?

First things first let’s dissect and define what .tap is doing here:

  1. .tap instantiates a new object and immediately pass it to a block. You see it right after object instantiation.
  2. Return value is automatically the object you modified (tapped).
  3. So it gets a new object a block level variable and whatever you tapped is the return value.

.tap eliminates the need for a local variable to instantiate an object and then having to pass a method. It sort of replaces having to do this:

animal = Animal.new("Dog")
animal.save

And instead do this:

Animal.new("Dog").tap{|dog| dog.save}

In this case we are tapping into the .send method.

Nice, what is .send?

  1. .send is used in Metaprogramming — cool
  2. Sends a method and delegates method call to .send — wait what?
  3. The first argument is the method. — so in the example above the “k=” method.
  4. The second argument is the value of the method — give me more…
  5. Send does mass assignment by writing code for us. That is why is used on Metaprogramming! — nice!

In the example above then the name of the key is the setter method and the value is the second argument passed.

Using .send can be seen in another example with an initialize method that takes in a collection of attributes with key/value pairs and then assigns those key/value pairs to the new instance. This can be pretty useful if we don’t know from the get-go which attributes the new instance might receive so we do:

def initialize(attributes)
attributes.each {|key, value| self.send("#{key}=", value)}
end

With .send we can write for future needs even with data we might not know about. In the case we receive different attributes from the currently defined we only have to add their particular attr_accessors. Which by the way did you know that the definition of an attr_accessor uses .send to write for us the particular reader/writer method(s)!

That’s so meta!

And now back to the story…

Yeah, coding the rest of the gem was as easy as seeing this cat play the piano:

Right.

Not really, I got into other problems.

But using Metaprogramming proved to be a nice tool that made my code be more effective, look nice and accomplish its goals.

Show me the code

From Coingecko’s Gem Source Code we can extract an example of how I used it to solve my initial problem of creating coin objects with the data received from the API. It goes like this:

def self.get_coin(coin_id)
Coingecko::Coin.new.tap do |coin|
Coingecko::API.look_up_coin(coin_id).each do |k,v|
coin.send("#{k}=", v)
end
@@all << coin
end
end

Here we are instantiating with .tap and then passing the API call method which then .send its associated key/values as attributes to the instantiated object I’m passing. So yup, just like this:

Social Entrepreneur and Software Engineer