Adam Lee
Adam Lee

Reputation: 25738

How to enable monkey patch for specific method?

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

Answers (3)

Jörg W Mittag
Jörg W Mittag

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

Shiva
Shiva

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

Shiva
Shiva

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:

  • You create AdaptorClass that Uses the Original class's behavior to provide you the new behavior you desire.
  • Now, you use the Adaptor's behavior rather than original class's to do your job where required.

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

enter image description here

Upvotes: 1

Related Questions