A Refinements Alternative
If you pay attention to Ruby, you almost certainly know about all of the refinements drama that happened. I’ll assume that you don’t though.
Basically, refinements is a feature that was going to be in Ruby 2.0, but has since been removed as a feature. There are a bunch of more thorough explanations of the idea behind refinements, but basically the idea is that you can monkeypatch a class, and those monkeypatches only exist within a certain scope of execution.
With refinements, you can add functionality to a class that is specific to the sender, without adding logic to the receiver that is not generally applicable.
So, here is something that lets you do this in a library. This doesn’t completely replicate native functionality, but it does let you get 90% of the way. Basically, we create classes that wrap other classes. First, the tests:
describe Refinery do
it "ignores classes that have not been refined" do
ref = Refinery.new
"lol".class.must_equal ref.refine("lol").class
end
it "refines" do
ref = Refinery.new do
refine String do
def to_ruby
"\"#{to_s}\""
end
end
end
ref.refine("hi").to_ruby.must_equal "\"hi\""
ref.refine("hi").length.must_equal 2
proc do
"hi".to_ruby
end.must_raise NoMethodError
end
end
And, the code:
require 'delegate'
class Refinery
def initialize(&refine_def)
@refinement_definition = RefinementDefinition.new
@refinement_definition.instance_eval &refine_def if block_given?
end
def refine(raw_object)
@refinement_definition.perform_refine(raw_object)
end
class SingleRefinementDefinition
def initialize(type, &defn_block)
@type = type
@refined_class = Class.new(SimpleDelegator)
@refined_class.class_eval(&defn_block) if block_given?
end
def matches?(obj)
obj.is_a? @type
end
def perform_refine obj
@refined_class.new obj
end
end
class RefinementDefinition
def initialize
@refinements = []
end
def refine type, &def_blk
@refinements << SingleRefinementDefinition.new(type, &def_blk)
end
def perform_refine obj
selected_ref = @refinements.select do |ref|
ref.matches? obj
end
if selected_ref.first
selected_ref.first.perform_refine obj
else
obj
end
end
end
end
I guess I think should put this somewhere better, but I’m not sure where, so I’m open to suggestions!