This is a gem which offers a helper method to create refinement-compatible modules. It's essentially a factory for modules which enables certain functionality while avoiding boilerplate.
Install the gem:
gem install refinement_builder
Require the code:
require 'refinement_builder'
There is one method contained here that is accessible in 3 ways:
-
Class method -
RefinementBuilder.build_refinement
:RefinementBuilder.build_refinement "StringPatch" do # ... end
-
Mixin
include RefinementBuilder build_refinement "StringPatch" do # ... end
-
Refinement
using RefinementBuilder build_refinement "StringPatch" do # ... end
As for what goes inside the block - normal methods, basically. These will be added as both instance and class methods (using module_function
) as well as refinements. Furthermore, the methods will be able to call each other with implicit namespacing from any of these contexts. For example:
RefinementBuilder.build_refinement "StringPatch" do
def print_first_5_characters(string=self)
print first_5_characters(string)
end
def first_5_characters(string=self)
string.slice 0...5
end
end
By defaulting the string
argument to self
, the methods are usable as String instance methods (using self
as the receiver instead of an argument) and StringPatch
static methods (passing the receiver as an argument).
The StringPatch
has its methods accessible in the same 3 ways as RefinementBuilder
-
Class method:
StringPatch.print_first_5_characters("hello world") # => hello
-
Mixin:
String.include StringPatch "hello world".print_first_5_characters # => hello
-
refinement:
using StringPatch "hello world".print_first_5_characters # => hello
refinement_builder
only cares about three arguments (and the block):
-
The name of the module constant to create (e.g.
"StringPatch"
in the above examples). -
The following keyword arguments:
namespace: <class/module>
where the module will be created under (defaults to Object)refines: <class>
what the refinement will patch. Also defaults to Object.
Under the hood, what happens is this:
- A module is defined with the given name and instance method definitions
- Each of the instance methods is overwritten in such a way that it will delegate to the class method if called from any other scope, and execute the original function body if it's in class method scope.
- The instance methods are copied to class methods via
module_function
- A refinement is defined with the instance methods.
The main benefit is the ability to get these multiple usages without writing wrapper functions. Here's a module definition which is about the same as the one created by this factory:
module StringPatch
def print_first_5_characters(string=self)
if eql?(StringPatch)
print first_5_characters(string)
else
StringPatch.print_first_5_characters(string)
end
end
def first_5_characters(string=self)
if eql?(StringPatch)
string.slice(0...5)
else
StringPatch.first_5_characters(string)
end
end
module_function :print_first_5_characters, :first_5_characters
refine String do
include StringPatch
end
end
In my opinion, this is no fun - too much boiler.