Ruby, celebrated for its dynamic nature and Metaprogramming capabilities, offers a wealth of powerful features that are often underutilized or misunderstood.
In this blog, we'll shed light on a fascinating topic within Ruby known as Monkey Patching.
We are going to cover:
- What is Monkey Patching?
- How can we apply Monkey Patching?
- What happens when Monkey Patching goes wrong?
- Is there an alternative approach?
Let's wear our Ruby hats and start diving into the concept of Monkey Patching.
What is Monkey Patching?
Ruby is a dynamic programming language and its interpreted language which gives you the ability to write or modify the code at Runtime.
Imagine you could add new abilities to existing things in Ruby.
That's what open classes or Monkey Patching.
Monkey Patch, lets you change or add things to existing stuff, like teaching a phone new tricks.
Why do we use it?
When you want to make something better without changing the original code.
How can we apply Monkey Patching?
Now we know what it is, let's dive into coding.
require 'date'
# TO check if date class has leap_year? method or not
p Date.methods.include?(:leap_year?)
# Output: false
class Date
def leap_year?
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
end
end
date = Date.new(2023, 10, 29)
p date.leap_year?
# Output: false
date = Date.new(2024, 2, 29)
p date.leap_year?
# Output: true
In this example, we open the Date
class, that's why it is called an Open Class.
You are adding a new leap_year?
method here that is called Monkey Patching.
What happens when Monkey Patching goes wrong?
While open classes are powerful, using them too much or in the wrong way can make things confusing or unpredictable.
Don't change the main things without thinking, as it might make others confused or stop things from working together.
Now let's understand this by example.
# Check if a method exists
String.methods.include?(:to_alphanumeric)
# Redefine class
class String
def to_alphanumeric
gsub(/[^\w\s]/, '')
end
end
p "Hello, @World!".to_alphanumeric
# Output: Hello World
p "Ruby is #1 for coding!".to_alphanumeric
# Output: Ruby is 1 for coding
Now whenever you are calling the to_alphanumeric
method on the String class you will get the same result.
This changes the global definition of the to_alphanumeric
method of the String class.
Now consider third-party applications/gems if they have the same method to_alphanumeric
for the String class, then it will behave differently, it will use your code instead of executing third-party applications or gem code.
We don't want this, to impact globally.
How can we solve this?
Is there an alternative approach?
Ruby 2.0 came up with a great feature to solve this issue. It's called Refinement.
What exactly is 'Refinement' all about?
It allows you to modify the behavior of a class or module temporarily while ensuring it doesn't impact the global definition.
Provides a way to extend a class locally.
Refinements can modify both classes and modules.
How can we implement the Refinement feature?
module ToJSON
refine Hash do
def to_json
'{' + map { |k, v| k.to_s.dump + ':' + v.to_s.dump }.join(',') + '}'
end
end
end
hash = {1=>2}
p hash.to_json
# Output: undefined method `to_json' for {1=>2}:Hash (NoMethodError)
Oops, an error is thrown.
What's the reason behind the error? The reason is it does not modify the global definition for the Hash class.
But hold on, we need these changes for the 'Hash' class, to fix this we must utilize the 'using' keyword.
module ToJSON
refine Hash do
def to_json
'{' + map { |k, v| k.to_s.dump + ':' + v.to_s.dump }.join(',') + '}'
end
end
end
module ConvertToJson
using ToJSON
def self.convert(hash)
p hash.to_json
end
end
hash = {1=>2}
ConvertToJson.convert(hash)
# Output: "{\"1\":\"2\"}"
Yay, we've accomplished this without affecting the global definition of Hash.
Let's discuss the Scope of Refinement:
- It's in the refine block itself.
- Code starts from the place where you call using until the end of the module.
Now you understand how Monkey Patching works, why Refinement is introduced, and how you can use it.
Refactor your Monkey Patches or Refinements, and review your code to ensure they still serve your project's goals.
If you want to use it, go ahead as now you know how you can use Refinement to prevent global scope.
Till we meet next time, Happy Coding!!
Top comments (0)