Ruby 2 - Refinements

Subscribe to receive new articles. No spam. Quality content.

Refinements has been added to Ruby to replace popular, but bad approach called Monkey Patching.

Let's check what Monkey Patching is and why it is bad.

For example, we have a class User, which has method to_s:

class User

  attr_reader :name

  def initialize(name)
    @name = name
  end

  def to_s
    "Name: #{name}"
  end

end

u = User.new("John")
puts u # => Name: John

Everything works fine, but at some moment your teammate decides to make something like that in other part of your application:

class User
  def to_s
    "User name is #{name}"
  end
end

Yes, Ruby is very flexible, so it allows you to change class at any place of your application. Since method to_s has been overwritten, result of execution will be different:

u = User.new("John")
puts u # => User name is John    

It's good if you have a small team and you can keep an eye on such changes. But it's getting even worse when such problem happens with gems you use. You use gem which expected to behave one way, but because of Monkey Patching it changes behaviour of classes unexpectedly.

I don't recommend using Monkey Patching. But if you have to change behaviour of method only in couple places of your app - it's much better to use Refinements.

Let's say that in all cases when we use User as a string, we should get something like: "User name is John", but in couple cases we need: "Name: John".

Let's add main to_s implementation to User class:

class User

  attr_reader :name

  def initialize(name)
    @name = name
  end

  def to_s
    "User name is #{name}"
  end

end

And for custom cases we just create a Module with refinement:

module RefinedUser

  refine User do

    def to_s
      "Name: #{name}"
    end

  end

end

Having this refinement, we can use it in cases where we need to get different behaviour of to_s:

class UserDecorator
  using RefinedUser

  def self.decorated_user
    puts User.new("John").to_s
  end
end

UserDecorator.decorated_user # => Name: John

User class will have custom implementation of to_s method just in scope of UserDecorator class, because of using RefinedUser refinement. In all other places main implementation of User#to_s will be used. This example is very basic, but shows the main idea of refinements.

All custom implementation of existing methods can be pulled out into Refinements. And you can use it in places where you need it. So you can get rid from Monkey Patching because Refinements is a much safer approach.

No more Monkey Patching!

Subscribe to receive new articles. No spam. Quality content.

Comments