Ruby Refinements
A refinement adds a method to an existing class inside one small scope. The added method answers only where the refinement is switched on with using.
A refined method inside one class
The Shout refinement adds shout to String. Greeter switches it on with using, so shout answers inside Greeter and nowhere else.
module Shout
refine String do
def shout
upcase + "!"
end
end
end
class Greeter
using Shout
def greet(word)
word.shout
end
end
puts Greeter.new.greet("hello")
puts "hello".respond_to?(:shout)Two refinements stay separate
Loud and Soft each refine String with a different method. Speaker uses only Loud, so soft never switches on at all.
module Loud
refine String do
def loud
upcase
end
end
end
module Soft
refine String do
def soft
downcase
end
end
end
class Speaker
using Loud
def say(word)
word.loud
end
end
puts Speaker.new.say("hey")
puts "hey".respond_to?(:loud)
puts "hey".respond_to?(:soft)Switching on for the rest of a file
using at the top level switches the refinement on for the rest of the file. Inside that scope, respond_to? agrees that the method exists.
module Wrap
refine String do
def wrapped
"[#{self}]"
end
end
end
using Wrap
puts "ruby".wrapped
puts "ruby".respond_to?(:wrapped)Example
module Prefix
refine String do
def tagged
"tag: " + self
end
end
end
class Label
using Prefix
def print(word)
word.tagged
end
end
puts Label.new.print("sale")
puts "sale".respond_to?(:tagged)Key points
- refine names the class that gains a method inside a module.
- A method defined under refine belongs to the refinement alone.
- using switches a refinement on for one class body or the rest of a file.
- Outside the using scope, respond_to? answers false for the refined method.
- Two refinements of the same class stay separate until each one is used.
In this example, the Prefix refinement adds tagged to String. Label switches it on, so print builds the tagged string. Outside Label, respond_to? answers false for tagged.
Note
A refinement never changes the class for the whole program.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
Inside a class that uses a refinement of String, word.shout answers. What does "word".respond_to?(:shout) answer outside that class?