DEV Community

Cover image for Using Refinements to Monkey Patch Safely
Oliver
Oliver

Posted on • Originally published at codeandclay.com

Using Refinements to Monkey Patch Safely

In Ruby, classes are open. They can be modified at any time. You can add new methods to existing classes and even re-define methods. Rubyists call this monkey-patching.

In the example below, I've added String#word_count to return the number of words in a string.

class String
  def word_count
    self.split.count
  end
end
Enter fullscreen mode Exit fullscreen mode
"Don't put your blues where your shoes should be.".word_count # => 9
Enter fullscreen mode Exit fullscreen mode

However, this change is global. What happens if someone else defines their own version of String#word_count based on different rules? Say, they might want to count the individual parts of a contraction, or ignore digits and any non-word characters.

Refinements allow us to monkey-patch classes and modules locally.

Here, I've created a new module and used Module#refine to add word_count to String.

module MyStringUtilities
  refine String do
    def word_count
      self.split.count
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

The refinement isn't available in the global scope:

"Take my shoes off and throw them in the lake.".word_count # => NoMethodError: undefined method `word_count' for…
Enter fullscreen mode Exit fullscreen mode

To activate the refinment, I need to use using:

using MyStringUtilities

"Take my shoes off and throw them in the lake.".word_count # => 10
"A pseudonym to fool him".word_count # => 5
Enter fullscreen mode Exit fullscreen mode

If I activate the refinement within a class or module, the refinement is available until the end of the class or module definition.

Below, the refinement is not available at the top level because it is scoped to MyClass.

module MyStringUtilities
  refine String do
    def word_count
      self.split.count
    end
  end
end

class MyClass
  using MyStringUtilities

  def self.number_of_words_in(string)
    string.word_count
  end
end

"Out on the wily, windy moors".word_count #=> NoMethodError

MyClass.number_of_words_in("Out on the wily, windy moors") #=> 6
Enter fullscreen mode Exit fullscreen mode

Top comments (0)