Ruby Refinements is a pretty new feature, introduced with Ruby 2.0.0. We decided to get into detail and tell you more about when and how to use it for your Ruby projects. Be the first to know!
What are Ruby Refinements for?
Ruby Refinements are a great feature that Ruby community has been waiting for a long time. It’s main purpose is to replace monkey patching, which is known for all sorts of negative side effects. It’s main advantage is the ability to set the scope, i.e. to specify the area of code it should affect.
Refinements are great for making your code easier to understand and comprehend.
It is not fit-it-all solution though, so let’s have a closer look at how it works.
How should I use Ruby Refinements?
To start working with Ruby Refinements you should first set the scope, which it should affect. So let’s define module and classes.
[ruby]
module ListStrings
refine String do
def +(other)
“#{self}, #{other}”
end
end
end
[/ruby]
After that add using ListStrings in the areas required
[ruby]
using ListStrings
‘4’ + ‘5’
# => “4, 5”
[/ruby]
Now you can do top level refinement of your code. You can also do it within eval statements.
[ruby]eval “using ListStrings; ‘4’ + ‘5’”
# => “4, 5″[/ruby]
Keep in mind that Using will let you affect only the current file, other files won’t be changed. Starting from Ruby 2.3 refinements can be used within specific classes, which is great, since it protects you from all sorts of adverse effects.
[ruby]module Foo
refine Fixnum do
def to_s
:foo
end
end
end
class NumAsString
def num(input)
input.to_s
end
end
class NumAsFoo
using Foo
def num(input)
input.to_s
end
end
NumAsString.new.num(4)
# => “4”
NumAsFoo.new.num(4)
# => :foo [/ruby]
What are Ruby Refinements Issues?
Ruby refinements will help you to introduce changes to the code safely. At the same time they still have a number of constraints, so please be attentive with them. Let’s have a look at some of them.
1. Multiple Refinements Will Fail
You won’t be able to run a few refinements for the same method name. They will simply fail. Though this is not a very common case, it’s better to keep this in mind.
2. Not Compatible with Dynamic Dispatch
Dynamic Dispatch is used for mapping method calls, e.g. [1,2,3].map(&:to_s). Unfortunately it’s not compatible with refinements yet.
3. Makes Checking Code History
Another important disadvantage is that refinements make it harder to say whether the code has been changed.It might be a good idea adding an instance variable to your refinement for better introspection.
[ruby]
module Bar
def refinements
require ‘set’
@refinements = (@refinements || Set.new) << Bar
end
refine String do
def bar
:bar
end
end
end
class A
include Bar and using Bar
def a
“”.bar
end
end
A.new.refinements
# => #<Set: {Bar}>
A.new.a
# => :bar
[/ruby]
4. Using Can’t Be Employed from within a Method or Module
If you want to go with refinements, you will have to declare Using for the scope set. On the other scope it might be useful for other developers or even you in a year time.
5. Top Level Experiments are Not Available
After version Ruby 2.3, you will be able to experiment with anonymous classes only.
[ruby]
module Fiz
refine Fixnum do
def +(other)
“#{self}#{other}”
end
end
end
class << Class.new
using Fiz
1 + 2
end
# => “12”
[/ruby]
So Shall I Use Them or Not?
Though Ruby Refinements, like any young feature, have some “child illnesses”, we still believe you should start using it right now. There’s extremely high probability that some of its drawbacks will be fixed in the nearest future, so there’s no necessity to focus on them right now.
Using Ruby Refinements you will be able to clearly show what the alternative behavior is and simply make your code more coherent.
So go try them now! And if you have any questions about them any other Ruby on Rails development, just contact us in the comments.