Meta-Programming — Class_eval vs Instance_eval

Lakhveer Singh Rajput
3 min readNov 29, 2023

--

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.

--

--

Lakhveer Singh Rajput

Ruby on Rails enthusiast, book lover and DevOps explorer. Follow me for insights on coding, book recommendations, and bridging development with operations.🚀📚