DEV Community

Roland Studer
Roland Studer

Posted on • Updated on

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
Enter fullscreen mode Exit fullscreen mode

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 fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

class Logger
  using Colorize
  def error
    "Ooops".red
  end
end
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

See the commit for adding colorize and using instance_eval

Latest comments (1)

Collapse
 
andyobtiva profile image
Andy Maleh • Edited

That is the reason I implemented Ruby Refinements in these libraries:
github.com/AndyObtiva/array_includ...
github.com/AndyObtiva/to_collection

I wanted to provide them as general object/array APIs that could be applied where needed only for safety and namespace pollution prevention reasons.