Reputation: 25738
I am using a gem, for some reason, one of its method needs to be patched before it can be used by some of my code.
The problem is here, how can I enable this patch just for some of my code, say. for some method inside a class, I need to enable this patch; some I want to disable this patch.
How to do this?
class FromGem
def BlahBlah
#patch here
end
end
class A
def Test1
#need patch
end
def Test2
# don't need patch
end
end
Upvotes: 2
Views: 1141
Reputation: 369428
This is what Refinements are for.
Say, we have the following third-party code:
class FromGem
def say_hello
'Hello'
end
end
FromGem.new.say_hello
# => 'Hello'
And we want to extend it to say "Hello World" instead, we'd do something like this:
module ExtendFromGem
def say_hello
super + ' World'
end
end
class FromGem
prepend ExtendFromGem
end
FromGem.new.say_hello
# => 'Hello World'
That's just the standard way of extending behavior, of course, this is still global. If we want to restrict the scope of our monkey-patch, we will need to use Refinements:
module ExtendedFromGem
module ExtendFromGem
def say_hello
super + ' World'
end
end
refine FromGem do
prepend ExtendFromGem
end
end
FromGem.new.say_hello
# => 'Hello'
# We haven't activated our Refinement yet!
using ExtendedFromGem
FromGem.new.say_hello
# => 'Hello World'
# There it is!
Now, what we want to write is this:
class A
def test1
using ExtendedFromGem
FromGem.new.say_hello
end
def test2
FromGem.new.say_hello
end
end
A.new.test1
# => 'Hello World'
A.new.test2
# => 'Hello'
Unfortunately, that doesn't work: Refinements only work in script scope, in which case Refinements are only active after the call to using
, or they work in module scope, in which case they are active for the whole module body, even before the call to using
, so what we can do, is this (IMO, this is cleaner):
class A
using ExtendedFromGem
def test1
FromGem.new.say_hello
end
end
class A
def test2
FromGem.new.say_hello
end
end
A.new.test1
# => 'Hello World'
A.new.test2
# => 'Hello'
or this:
class A
def test2
FromGem.new.say_hello
end
end
using ExtendedFromGem
class A
def test1
FromGem.new.say_hello
end
end
A.new.test1
# => 'Hello World'
A.new.test2
# => 'Hello'
Et voilà: test1
sees the refinement, test2
doesn't.
Upvotes: 8
Reputation: 12514
This could also be a solution
class <<
is used to modify a particular instance; a part of meta-programming
class A
def print
p 'Original method'
end
end
class B
def initialize
@new_instance_of_a = A.new
end
def my_method
modifiable_a = A.new
# modifying the instance of class A i.e. modifiable_a
class << modifiable_a
def print
p 'Monkey patch'
end
end
modifiable_a .print
end
def normal_method
@new_instance_of_a.print
end
end
However, modifying a local_instance
does not make more sense. If this modified instance could be used in some more places then using this method is worthy
Upvotes: 1
Reputation: 12514
If you want to change the behavior just in one place, then you do not need anything to do, just write few lines of code that meets your requirement using or without using the gem's method
If you are gonna need that modified behavior in multiple places then creating a method that implements the changed behavior. In this case; you are gonna use Adapter Design pattern;
Steps:
Try not to modify the original class; follow Open-Closed Principle
class A
def Test1
# need patch
# you use GemAdapter rather than FromGem
end
def Test2
# don't need patch
# use FromGem class
end
def Test3
# need patch
# you use GemAdapter rather than FromGem
end
end
class GemAdapter
def new_behavior
gem_instance = FromGem.new
# implement your new behavior you desire
end
end
Upvotes: 1