• Home
  • About
    • Coding Recipes photo

      Coding Recipes

    • Learn More
    • Email
    • Twitter
    • Facebook
    • LinkedIn
    • Instagram
    • Github
  • Posts
    • All Posts
    • All Tags

Monkey patching & refinements

30 Mar 2020

Reading time ~2 minutes

A couple of months ago, I had to use the existing class with couple of tweaks and even though I ended up using different solution, I found this one still interesting. One of my first thoughts was start with monkey patching. Monkey patch means redefining or adding functionality to the existing classes. But that doesn’t come free, the price you pay is that you can risk some unintended bugs or breakages because all users of the monkey-patched class see the same changes.

Thankfully, since Ruby 2.0 this can be done in much safer way using refinements. Refinements are activated until the end of the current class or module definition, or until the end of the current file if used at the top-level.

Lets say we have a class that changes input string to uppercase or lowercase:

class CapsSwitch
  attr_reader :name

  def initialize(name:)
    @name = name
  end

  def up
    name.upcase
  end

  def down
    name.downcase
  end
end

We are being a little bit mean and we want to redefine method down which instead of returning lowercase it returns the lenght of the string, but also we want to redefine initialize method to accept the new additional parameter (surname).

module RefinementModule
  refine CapsSwitch do

    def initialize(surname: nil, **kwargs)
      @surname = surname
      super(**kwargs)
    end

    def down
      @surname.size
    end
  end
end

If we make a new class which will use our RefinementModule, we won’t get the size because refinements are only active in the current scope and CapsSwitch can’t be initialized with the new parameter.

class SizeClass
  using RefinementModule

  def initialize(name:, surname:)
    @name = name
    @surname = surname
  end

  def size
    CapsSwitch.new(surname: @surname, name: @name).down
  end
end

Instead, we can create an additional class inherited from CapsSwitch class. This will allow us to have additional parameter initialized.

class CapsSwitchWithSize < CapsSwitch
  using RefinementModule
end
class SizeClass
  def initialize(name:, surname:)
    @name = name
    @surname = surname
  end

  def size
    CapsSwitchWithSize.new(name: @name, surname: @surname).down
  end	 
end

In general, I think avoiding monkey patching is the best technique, but sometimes you just need quick solution and we are all only humans after all. :)



clean codesoftwarecodemonkey patchrefinements