Demystifying MetaProgramming in Ruby

  • Meta-programming is writing code that manipulates language construct at runtime. It reduces the number of lines of code by a large extent which helps the developer in writing and maintaining the code much easier. As a dynamic language, Ruby gives you the freedom to define methods and even classes during runtime. There is a saying that everything in Ruby is an object (expect methods).
  • Some uses of meta-programming are — we can remove methods at runtime, make object respond to message which user understands to be methods but there is no such method. To understand meta-programming there are few terminologies which need to be understood.
  • Module — A module is a collection of methods and constants. The methods in a module may be instance methods or module methods.
  • Class — class is an object (instance of “Class”). At, the same time its a module with three additional instance methods i.e. (new, allocate and superclass) that allows you to create objects or arrange classes into hierarchies.
  • The main reason for having a distinction between modules and classes is clarity, by picking either a class or a module, we can make our code more explicit. We pick a module when it is meant to be included somewhere, and we pick a class when it is meant to be instantiated or inherited.
Classes and Object Hierarchy in Ruby
  • Understanding Ghost methods, perquisites — method_missing and respond_to?
  • method_missing — When a method is called and it is not found in the interpreter , then, at last, it goes to this method defined in BasicObject.
  • respond_to — Returns true if obj responds to the given method. Private methods are included in the search only if the optional second parameter evaluates to true.
  • The concept of overriding the method_missing such that the object responds to call which does not exist as a method, is called the ghost method.
  • It is very useful in cases where we need to define similar methods, and hence can be done at a single place.
  • Ghost method, as the name suggests is not actually a method but a trick which makes programmer think of the call as a method which in fact is NOT.
Example of Ghost method
  • In the above-illustrated example, there is no such method as call_ghost_method but overriding method_missing we can trick the user into believing it to be a method.
  • Since the ghost method itself is not a method respond_to? and self.class.instance method does not have it.
  • Now respond_to method calls a respond_to_missing method, so we can override the respond_to_mssing method to get our desired result.
Example of overriding respond_to_missing
  • Ability to re-open any class and change its methods is called monkey-patching.
  • The user can override or add new methods to the class using monkey patching.
Example of MonkeyPatching
  • MonkeyPatching is dangerous. Since the user can override the methods predefined, hence it may lead to unexpected behaviour of the classes and could be extremely difficult to trace and rectify the bug.
  • A better way of using Monkey-patching is using refinement.
Example of refinement
  • Adding and removing methods dynamically
  • Using define_method :method_name {|*args| your_method }
  • The above way of defining a method over runtime is called Dynamic method.
  • remove_method and undef_method
  • remove_method removes the inherited + defined ones, whereas the undef_method removes only defined ones
  • Block, procs and lambdas — Family of callable objects
  • Block is anything in ruby within {} or do.. end. It is always called with a method, and evaluation is done using yield keyword.
Block example
  • block_given? can be used inside the method to check if the block is passed.
  • Procs are similar to block but instead are stored in an object and can be run by using the call method. They can be defined using Proc.new { |arg|… } or proc {|arg| … }
Proc example
  • Lambda is similar to proc with the difference being between reading arguments and handling return keyword. Lambdas can be declared by using lambda {|arg| … } or -> (arg) { …. }
Lamda example
  • Due to its argument handling nature, lambdas are preferred mostly as it resembles methods.
  • Procs handle arguments silently, i.e. if the number of arguments is more or less than expected, no error is thrown. If it is more than the required, the extra arguments are ignored and if it is less than required the left out arguments are given nil value.
  • Lambda would throw an error if no of arguments passed does not match expected arguments.
  • The return keyword returns the value in case of lambda, whereas in the case of proc it returns the scope. The skipping way for this is not to use return in case of proc.
  • At last, metaprogramming can be very useful at the same time it can be dangerous also. Understanding the right context and its use is very important before implementing it.