A Reckless Example of Using Basic Meta-programming Concepts and How it Paid Off
Just before we begin, I wanted to give you a fair warning. The example i’m going to use will be a case of using TTY-Prompt where my user input was controlled. Because it was controlled , the returning values were controlled as well. This will save you energy so long as you avoid being careless. This can work if you loose up some of the user input restrictions, but just know that the possibility of something going wrong becomes greater. If you act too reckless, this will bite you in the butt.
As a fairly new student/practitioner of Ruby, there are some things about this language that just seem nice and makes things easier to read. The syntactic sugar sometimes feels a bit sweeter using this language compared to others. But even so, one of the first things they always teach you about Ruby that is easy to forget is that it is meant to be dynamic. It’s easy to forget as you begin to learn the language, but once you make that true realization, you’ll notice a lot of paths will open up for you. The following is an example of recklessly applying this to a user’s input to clean up my code and open up the path of making it even more modular.
At the last week of the Ruby section for my coding boot camp, I was put into a group with some classmates and asked to come up with a CLI application that used 3 models together. The goal was to show our understanding of CRUD & ActiveRecord. While making this, we made the decision that instead of just using puts
and gets
to spit out information and get the user’s reply, we would use TTY-Prompt (which i totally recommend). To cut to the chase, the gem would allow me to call a prompt, feed it a question and an array/hashes, and have it call a method based on the result. By using TTY-Prompt I was able to mash a bunch of puts
& gets
into one line with the added bonus that I can feed a hash where the hash[key] would be read to the user as a choice while the hash[value] would be returned to me. It opened the ability to use case
on the results and lead it to other methods within the code, ultimately mimicking a real interactive menu.
Our initial mission was to get the code working, but there was something that was bothering me. We had similar code through out our CLI app where it was they kept referring to the same model, but they just called a different method onto it to either access an attribute or to a refer to the same instance. Because we were trying to be a bit nicer and have things easier to read for the future, our app ended up having somewhere around 415 lines!!! Hopping from section to section became more tedious just because of the number of lines in the code vs what was actually happening. The program just looked bloated. Here is where we can use some<basic concepts of >META-PROGRAMMING! to make things neater and a little bit cooler.
Let’s look at an example from my original code. In the program, there was a class method called analytics_page
which would execute another Class#method from a different model which ultimately puts
something. TTY-Prompt asks a question, I gave an array which returns the inside element once the user chooses it. I would then see what that value was, and execute the respective method.
Quick aside, TTY-prompt#select will accept a hash as the second argument,and will automatically make the hash’s keys to choices for the user to select, and will return whatever the hash’s values. But the logic can still be applied outside of TTY-Prompt. you just take whatever you receive(gets or received from outside) from the user and search the hash to see it exists and return the value***
Revisiting the code, I felt I could make it a bit more dynamic and cleaner. My solution was to create a hash with an array as the values. Then based on what the input was received, I can look at the first value in the value array, and see if it is genre, artist, or user. I can then call#send
with the second element in the value hash to the respective model and execute whatever was the second value of the array on the respective class.
Revisiting the code, I felt I could make it a bit more dynamic and cleaner. My solution was to create a hash with an array as the values. Then based on what the input was received, I can look at the first value in the value array, and see if it is genre, artist, or user. I can then call #send
with the second element in the value hash to the respective model and execute whatever was the second value of the array on the respective class.
This is already making the code a bit leaner and using less checks, but we can go even further. I still have to do a check to see which class i want to direct it to. If i can get rid of that, i can take out those case/whens and maybe keep it all into one line. This is where another Object method comes in handy, Object.const_get()
. Since in my hash, I already have a value that is an array, I can make one of the elements a string of the Class name I want to search for. When I feed that string to Object.const_get
and chain send()
to it, I can now dynamically call any class and any method I want, including the ones I came up with in the menu_hash.
Now doing all this based on user input is a reckless, because who knows what we might receive. The potential downside to this is if you loosen up what you pass to these arguments, you open your program up for errors, or to allow someone pass in a code you don’t want. In the examples above, since i was using #send
after a TTY:Prompt #select
asking the user for input, I was better able to control what was being passed. In a way, the inputs were sanitized. The more “open” the input is, the more restrictive you will want to be on how you use that input. But if you’re feeling extra reckless and brave, you can take user input directly, and chain things like define_method
or check the string against public_class_method
. But that dive into Metaprogramming will have to come another day.