Metaprogramming, the art of messing with the internals of a programming language, is one of the most powerful learning tools an aspiring programmer can utilize. It's very easy to take the workings of the given language as they are for granted, but there's hardly any room for improvement in doing so. Quite the contrary, being oblivious to how the things work behind the scenes may make you unable to grasp many of the concepts that you may use in the project on daily baisis. Unfortunately for metaprogramming, as the most the projects don't really delve into this arcane realm, the opportunity to put it into use may not come for a long time, if ever.
This is not good. Let's warp the reality and create our own opportunities. What else could you possibly do in your free time anyway? Play video games? What a nerd.
For the higher purpose of expanding our knowledge, we must forget (just a little) about practical usage of the forecoming examples. For now, only our goal matters and the means of how we achieve it. Let's say, that for some weird, possibly sexual reason you'd want to create an initializer for your class, that would take any number of hashed arguments and create corresponding accessors for each of them. Why would you want to do that? I don't know nor care, but we sure as hell are going to do that.
So we want this:
to be as viable as this:
And to be able to access values of these variables like this:
And set them like this:
So, to unpack it all: we need to make our initialize method accept a hash of arguments, iterate through them, set them as instance variables and create an accessor for each of them. Let's try this:
Looks good. Double splat operator (**) is an indicator of arguments being processed as a hash. 'Instance variable set' is our go-to way of...you know, it's pretty self-explanatory, so I'll just let you figure that out yourself. The last line is the call to our class' private method with an arguments being names of the variables that we previously set as the instance variables and which we want to be able to access or set. OK let's find out what the fallout is:
Ok, seems to be working well...if you are willing to disregard this:
You see, our 'send' method have been acting directly on our class, so the accessors we set like this aren't going nowhere and are freely available to all future instances of this class. This is a mess and we can't have that. Accessors we set should be constricted only to the arguments which we initialize our class instances with and available only for given instance. So what we really need to do, is to kinda cheat our way into the accessor-like behaviour by creating per-instance methods which simply return out previously set instance variables or set new values for them. Here comes the question: did you know that singleton methods can be created for instances as well? I know what you'll think: it's almost ultra rare in the environment of run-of-the-mill projects. Most certainly, but why bother with boring reality, when we can create our own occurrences? Let's do this right now:
This time we completely make do without 'attr_accessor' business. Instead, iterating through our arguments we create two singleton methods per argument - one of them simply named as your variable name which simply call the inverse of previously used 'instance_variable_set' - 'instance_variable_get' to get its' value, second - with equal sign following the variable name (and also acccepting additional argument) for setting new value to our variables. Let's find out where the whole affair has gotten us:
Voila. No access to the first instance's variable from the second one, getters and setters working as intended. Now, as far as metaprogramming goes, this is a pretty basic and primitive example, but it's only meant to show that generally, programming languages hold many neat tricks, that can enable you to bend the rules of the game to your will.
In the next blog post we will drop corporate shackles of predefined modules and create our own lite and gluten-free version of ActiveSupport::Concern. Stay tuned!