Tuesday, October 1, 2013

MetaProgramming using Ruby

The Object Model:

See any ruby program and you will see objects everywhere. It includes language constructs such as classes, modules, variables, instance variables , class variables, methods etc. All these constructs together make the object model each playing some role. 

With the help of object model we are able to associate objects to classes or methods to classes and many many such questions. So lets dive into the first concept of metaprogramming using Ruby.

1) Open Classes:

Monkeypatching is not adviced.

 class String  
      def wow  
           "This is amazing ain't it"  

We opened the String class and we plant the wow method there which returns the text "This is amazing ain't it"

Once you do this, every string in the system will be exposed to the wow method.

Similarly you can open any class and add methods to it and it will be available to all the instances of that class.

The example above doesn't really make sense here but it shows the power of ruby wherein you can open any class and add methods to it as you desire

2) Inside Class definitions:

 3.times do   
      class C  
           puts "Wow"  

In Ruby there is no real distinction between code that defines a class and code of any other kind.
You can put any code you want in a class definition.

Above code will execute the code inside do...end block will be executed thrice but does that mean it defines class C three times?

Lets see another example:
 Class Amit  
   def first  
     puts "Amit"  
 Class Amit   
   def second  
     puts "Acharya"  
 a = Amit.new  
 a.first  #=>  "Amit"  
 a.second #=>  "Acharya"  

When the class Amit comes into the picture for the first time, no class by that name exists yet. So, Ruby steps in and defines the class and the first method with it.

In the second Definition of class Amit, the Class already exists, so Ruby doesn't need to define the class again, As learned earlier, it uses Open Classes concept to open the method and define the second method insice class Amit.

The core job of the class keyword is to give you the context of the class where you can define methods.

3) Real life example of Open Class

You can convert any number to Money object by calling Numeric#to_money()

amount = 100.to_money()

so to_money methods has to be defined in the Numeric class (100's class)

Following is the snippet from the money gem where in it opens the Numeric Class and adds the to_money method, similary you can find the same in the String class so you can do "100".to_money()

1:  # Open +Numeric+ to add new method.  
2:  class Numeric  
3:   # Converts this numeric into a +Money+ object in the given +currency+.  
4:   #  
5:   # @param [Currency, String, Symbol] currency  
6:   #  The currency to set the resulting +Money+ object to.  
7:   #  
8:   # @return [Money]  
9:   #  
10:   # @example  
11:   #  100.to_money          #=> #<Money @cents=10000>  
12:   #  100.37.to_money        #=> #<Money @cents=10037>  
13:   #  BigDecimal.new('100').to_money #=> #<Money @cents=10000>  
14:   #  
15:   # @see Money.from_numeric  
16:   #  
17:   def to_money(currency = nil)  
18:    Money.from_numeric(self, currency || Money.default_currency)  
19:   end  
20:  end  

4) Problem with Open Classes
It is very easy to accidentally overwrite an existing method definition and that could cause nasty bugs,
e.g. if you replace the String#length() with your own version of length(), that could cause unwanted bugs in the system.

No comments:

Post a Comment