16. 11. 2020

Ruby refinements have a second good use case.

According to Bradley Refinements have one good use case: Conversion Wrappers. I humbly present the second one.


Monkey patching in Ruby is a simple as:

class String
  def red
    "\e[#31m#{self}\e[0m"
  end
end

But what if you don’t to pollute the String-class for users of your Gem? In the worst case you might actually overwrite a monkey patch provided by the developer. We faced this when we wanted to provide coloring of strings for our logger in StimulusReflex. We wanted our users to be able to write a configuration proc like this:

config.logging = proc { "#{timestamp.cyan} #{reflex_id.red}" }

Enter refine

With refinements we were able to monkey patch only where explicitly called for it:

module Colorize
  refine String do
    def red
      "\e[#31m#{self}\e[0m"
    end
  end
end

Now you can activate this monkey patch in whatever class you need it:

class Logger
  using Colorize
  def error
    "Ooops".red
  end
end

A complication

Procs are a convenient solution, however they get the binding/context of where they are defined, not of where they are called. Just calling the proc in our Loggerclass will not make use of the colorize-refinement, actually no method of the Loggerclass is available to the proc, because it was defined in a completely different context (in an initializer of the gem user).

So we made use of instance_eval

class Logger
  using Colorize
  def log
    puts instance_eval(&config.logging)
  end
end

See the commit for adding colorize and using instance_eval


Originally posted on dev.to