Refinements
Monkey patching with limited scope
Section titled “Monkey patching with limited scope”Monkey patching’s main issue is that it pollutes the global scope. Your code working is at the mercy of all the modules you use not stepping on each others toes. The Ruby solution to this is refinements, which are basically monkey patches in a limited scope.
module Patches refine Fixnum do def plus_one self + 1 end
def plus(num) self + num end
def concat_one self.to_s + '1' end endend
class RefinementTest # has access to our patches using Patches
def initialize puts 1.plus_one puts 3.concat_one endend
# Main scope doesn't have changes
1.plus_one# => undefined method `plus_one' for 1:Fixnum (NoMethodError)
RefinementTest.new# => 2# => '31'Dual-purpose modules (refinements or global patches)
Section titled “Dual-purpose modules (refinements or global patches)”It’s a good practice to scope patches using Refinements, but sometimes it’s nice to load it globally (for example in development, or testing).
Say for example you want to start a console, require your library, and then have the patched methods available in the global scope. You couldn’t do this with refinements because using needs to be called in a class/module definition. But it’s possible to write the code in such a way that it’s dual purpose:
module Patch def patched?; true; end refine String do include Patch endend
# globallyString.include Patch"".patched? # => true
# refinementclass LoadPatch using Patch "".patched? # => trueendDynamic refinements
Section titled “Dynamic refinements”Refinements have special limitations.
refine can only be used in a module scope, but can be programmed using send :refine.
using is more limited. It can only be called in a class/module definition. Still, it can accept a variable pointing to a module, and can be invoked in a loop.
An example showing these concepts:
module Patch def patched?; true; endend
Patch.send(:refine, String) { include Patch }
patch_classes = [Patch]
class Patched patch_classes.each { |klass| using klass } "".patched? # => trueendSince using is so static, there can be issued with load order if the refinement files are not loaded first. A way to address this is to wrap the patched class/module definition in a proc. For example:
module Patch refine String do def patched; true; end endend
class Fooend
# This is a proc since methods can't contain class definitionscreate_patched_class = Proc.new do Foo.class_exec do class Bar using Patch def self.patched?; ''.patched == true; end end endendcreate_patched_class.callFoo::Bar.patched? # => trueCalling the proc creates the patched class Foo::Bar. This can be delayed until after all the code has loaded.
Remarks
Section titled “Remarks”Refinements are scope lexically, meaning they’re in effect from the time they’re activated (with the using keyword) until control shifts. Usually control is changed by the end of a module, class, or file.