Code & Clay – Notes to self. Mainly Ruby/Rails.

Monkey-patching locally

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
"Don't put your blues where your shoes should be.".word_count # => 9

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

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…

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

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