Meta-Programming — Class_eval vs Instance_eval
In this article, we will get to know how the instance_eval and class_eval methods work and how can we use them. This article has been a continuation of the previous article on metaprogramming in ruby. Ruby has provided ways by which we can change the behaviors(methods) of the class and object dynamically at any point. These two methods are the one of ways through which this can be achieved.
instance_eval
First, let us know about the instance_eval method. The instance_eval method in Ruby provides a way to change or modify behaviors (methods) of an object of any class(Even though it can be the BasicObject of the previous article). Let’s have a look into it through an example :
We have a Player class for which we will define certain methods :
class Player
def initialize(name, game, date_of_birth)
@name = name
@game = game
@date_of_birth = date_of_birth
end
def name
@name
end
def game
@game
end
def date_of_birth
@date_of_birth
end
end
player = Player.new("John", "Football", "12-07-2000")
puts "Name: #{player.name} \nGame: #{player.game} \nBirth Date: #{player.date_of_birth}"
#Name: John
#Game: Football
#Birth Date: 12-07-2000
Now, We have to update a specific method on the object football? which will return true and false. Through the instance_eval method, we can perform the following action:
player.instance_eval do
def football?
@game.upcase == 'FOOTBALL'
end
end
puts player.football?
#true
player2 = Player.new("William", "Football", "12-07-2000")
puts player2.football?
#`<main>': undefined method `football?' for #<Player:0x0055dc951af9b0> (NoMethodError)
Thus, we added a method(behavior) to the object but it had been added as a singleton method. Now when we create a new object of the same class the method will be not available. Now suppose we had applied the instance_eval directly to the class Player:
Player.instance_eval do #Added as class method
def football?
puts self
end
end
Player.football?
# Player
puts player.football?
#`<main>': undefined method `football?' for #<Player:0x0055dc951af9b0> (NoMethodError)
When we try to modify the class directly instead of an object through the instance_eval method, the methods(behaviors) will be added as class methods. Thus, the instance_eval can create both types of methods(class and singleton).
class_eval
The class_eval method is a straightforward method that works on the class to modify the behavior of objects created from the class. Simply, we are adding new behavior to the whole class, not a particular object. Let’s say we need to add a method called age in the above example, it will calculate the age through the date of birth.
Player.class_eval do
def age
birth_date = Date.parse(@date_of_birth)
today = Date.today
today.year - birth_date.year - ((today.month > birth_date.month || (today.month == birth_date.month && today.day >= birth_date.day)) ? 0 : 1)
end
end
player = Player.new("John", "Football", "12-07-2000")
puts player.age
#23
player2 = Player.new("William", "Football", "12-07-2004")
puts player2.age
#19
player.class_eval do
def age
birth_date = Date.parse(@date_of_birth)
today = Date.today
today.year - birth_date.year - ((today.month > birth_date.month || (today.month == birth_date.month && today.day >= birth_date.day)) ? 0 : 1)
end
end
#NoMethodError: undefined method `class_eval' for #<Player:0x0055ed368bf320>
Above we can see that the age method returns the age of the player object created from the Player class but when we use class_eval with the object it throws an error. The above code is the same as writing :
class Player
def age
birth_date = Date.parse(@date_of_birth)
today = Date.today
today.year - birth_date.year - ((today.month > birth_date.month || (today.month == birth_date.month && today.day >= birth_date.day)) ? 0 : 1)
end
end
To be continued
They are not used in day-to-day development but if we keep updated with them they help us with DRY convention. Suppose we are required to update the object every time a particular API hits in rails with a new behavior without changing the basic structure of the class we are using. It will help in getting dynamically written methods when needed on the API hit. Then, again it will be returned to its original state. This is just an example, we can use them in more fantastical ways. After this, we will continue with other metaprogramming aspects like define_methods, send, etc.