Metaprogramming Ruby: Core Concepts - Extensions
As we already know, ruby provides several ways to extend our classes and objects including but not limit to OO inheritance, Module mix-in, Dynamic Methods, Monkey patching / Refinement, Eval family, Ghost methods, Callable objects, Singleton classes / methods, etc. But there are still some important tips deserve to be mentioned, before we moving forward.
1. Include vs Extend
Firstly let’s see an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
It seems that module mix-in can only involve instance methods but not others like class methods. But how to do similar thing at class level? You must hear about extend:
1 2 3 4 5 6 7 8 9 10 11 |
|
Extend is used in object level, so we are sure it can also be used for any class in ruby. But in previous article, we know that class method is actually saved as a singleton method of the original class, also instance method of its singleton class. So that should also happen on include:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Thus we can regard extend as a kind of special usage on include through above examples.
2. Method wrappers
Method wrapper means wrapping an existing method inside a new method, which is very useful when you want to make extension without changing the source, like code in standard library or other cases.
There are several ways to implement method wrappers in ruby, and they are all in composite form of primitives which’ve already been introduced in previous articles. We’ll go through below.
Around Alias
Module#alias_method (also the key word ‘alias’) is used to give another name to ruby methods. It will involve more accessibility if an usual method could have different domain names(e.g. size, length and count). Also more flexibilities if you want, like code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
This is just a kind of wrapper using open class, alias_method, and method redefinition.
Refinement
We talked about refinement and suggested using refine instead of monkey patch. Actually refinement is even more powerful than that.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Only thing you need to notice is that the key word ‘using’ may not work well with your IRB environment, which means you couldn’t get result in mind for some versions of ruby if you run those code in IRB. See more information here.
The benefit of refinement wrapper is controllable scope of wrapper unlike around alias which affects globally. However, accessibility to original method is also lower than alias way.
Prepending
Module#prepend is the simplest way to implement method wrapper without scope configurability like refinement. But much more clear than other two.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
3. Class Macros
Ruby objects have no attributes - May this won’t surprise or confuse you too much. Indeed we’ve hear about instance variables or class variables, but you can not access them directly from outside. That means getter or writer can not be avoided:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
It’s just not ruby style! Ruby provides series accessors for class definition using metaprogramming api, like attr_accessor, attr_reader and attr_writer, they are quite intuitive to use:
1 2 3 4 5 6 7 |
|
attr_* come from Module as private instance methods, thus they all can be used in module or class definitions.