Introduction
Ruby is a popular object-oriented programming language known for its expressiveness and flexibility. One of the unique features of Ruby is its support for metaprogramming, which allows developers to write code that can modify and extend the behavior of a program at runtime. Metaprogramming in Ruby involves using techniques such as defining methods dynamically, modifying classes and objects, and leveraging the reflective capabilities of the language. In this blog post, we will explore the define_method and define_singleton_method Both these methods accept the method name as an argument and block to define the functionality or return value, but there is a slight difference between them.
Let’s see how these two methods work differently
define_method
Using define_method, you can dynamically add a method to a class instance. The define_method method takes two arguments: the name of the new method to be defined as a symbol or a string, and a block that contains the code to be executed when the new method is called. To better understand this, let’s take a look at an example.”
1
2
3
4
5
6
7
8
9
class Dummy
define_method 'my_method' do
puts "Executing 'my_method'"
end
end
dummy = Dummy.new
dummy.my_method
# => Executing 'my_method'
The key point I would like to make is that define_method is only available at the class level, and attempting to create a method from an instance will result in an error.
However, it is important to use define_method with caution, as it can also lead to unexpected behavior if not used properly. Let’s understand this with following example.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Car
def self.build(color)
define_method 'start_building' do
puts "Starting a new car build with a color #{color}"
end
end
end
red_car = Car.new
blue_car = Car.new
red_car.class.build('Red')
red_car.start_building
# => Starting a new car build with a color Red
blue_car.class.build('Blue')
blue_car.start_building
# => Starting a new car build with a color Blue
If you observe closely, you’ll notice that our build
method is a class method. This is because define_method is only available as a class method, not as an instance method. Attempting to convert build into an instance method will result in a NoMethodError.
So far, everything is looking good, but when you call start_building
on red_car
again you will see some issues with output.
1
2
red_car.start_building
# => Starting a new car build with a color Blue
You can fix by moving color parameter as follows
1
2
3
define_method 'start_building' do |color|
puts "Starting a new car build with a color #{color}"
end
define_singleton_method
We can also create methods at runtime using define_singleton_method, but this function works a bit differently. Let’s see how we can create a method using define_singleton_method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Car
def build(color)
define_singleton_method 'start_building' do
puts "Starting a new car build with color #{color}"
end
end
end
red_car = Car.new
blue_car = Car.new
red_car.build('Red')
red_car.start_building
# => Starting a new car build with a color Red
blue_car.start_building
# => NoMethodError: undefined method 'start_building' for Car:Class
blue_car.build('Blue')
blue_car.start_building
# => Starting a new car build with a color Blue
I’d like to point out two things here:
- We need to explicitly call
build
on each object; otherwise, we will get an error when calling the dynamic method. - define_singleton_method is available as both an instance method and a class method. However, the level of the parent class determines the level of the dynamic method. To better understand this, let’s compare the following dummy code snippets
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class DummyKlass
# Method will be added as a class method
define_singleton_method 'my_method' do
"Executing 'my_method'"
end
end
class DummyInstance
def build
define_singleton_method 'my_method' do
"Executing 'my_method'"
end
end
end
klass_test = DummyKlass.new
klass_test.my_method
# => undefined method `my_method' for #<DummyKlass:0x0000000113202e48> (NoMethodError)
klass_test.class.my_method
# => "Executing 'my_method'"
instance_test = DummyInstance.new
instance_test.my_method
# => "Executing 'my_method'"
instance_test.class.my_method
# => undefined method `my_method' for DummyInstance:Class (NoMethodError)
DummyKlass
is executing define_singleton_method at a class level, which adds my_method as a class method.DummyInstance
is executing define_singleton_method from an instance method, which adds my_method as an instance method and not as a class method.
Summary
- We can create methods in Ruby at runtime using define_method and define_singleton_method
- define_method has to be executed by class and it creates the instance method.
- define_singleton_method can be called from class or from class instance. If it is called from class then new method will be added as a class method and if called from an instance, then new method will be added as an instance method, to only an instance executing it.
- Be cautious while adding defining methods as this may lead to the unintended behaviour.