Page Content

Tutorials

What Is Refinements In Ruby, Purpose And Characteristics

With Ruby 2.0, a feature called refinements was added to limit the extent of changes that may be made to already-existing classes, making it a safer option than monkey patching.

Concept and Purpose of Refinements in Ruby

Polluting the global scope is the primary problem with traditional Monkey Patching, which involves reopening a class like String or Fixnum to add or modify methods. Your code may encounter conflicts if two distinct modules try to alter the same standard class (stepping on each other’s toes) when you monkey patch globally.

By offering monkey patches with a restricted scope, refinements address this issue. These are methods that are specified in a unique module construct and only work when the using keyword is used to specifically activate them.

Key Characteristics

Limited Scope: Lexical scope applies to refinements. They take effect immediately upon activation (by using the using keyword) and continue to do so until control changes, which often occurs at the conclusion of a module, class, or file definition.

Activation: The using keyword is used to activate them, and it has to be used in a class or module definition.

Definition: The class to be changed is generated after the refine keyword, which is used to build the definitions themselves inside a module. It is only possible to use the refine keyword within a module scope.

You can also read What Are The Ruby Version Management With Code Examples

Implementation and Code Examples

Defining a Refinement

A refinement is defined by creating a module, opening an existing class (such as String or Fixnum), and adding additional methods using the refine keyword.

Code Example 1: Defining a Refinement Module

With the addition of methods like plus_one and concat_one, this module defines a refinement for the Fixnum class:

module Patches
  refine Integer do
    def plus_one
      self + 1
    end

    def plus(num)
      self + num
    end

    def concat_one
      self.to_s + '1'
    end
  end
end

# To activate the refinement:
using Patches

puts 5.plus_one      # => 6
puts 10.plus(5)      # => 15
puts 7.concat_one    # => "71"

Output

6
15
71

Activating the Refinement (using)

Until the using keyword is used to include the refinement module into a class or module, the methods specified inside it remain inactive.

Code Example 2: Activating Refinement within a Class

The following RefinementTest class makes use of the Patches module, which restricts access to the refined methods to its scope.

module Patches
  refine Integer do
    def plus_one
      self + 1
    end

    def concat_one
      self.to_s + '1'
    end
  end
end

class RefinementTest
  using Patches  # Refinements active only inside this class

  def initialize
    puts 1.plus_one      # => 2
    puts 3.concat_one    # => "31"
  end
end

# --- Outside refinement scope ---
begin
  puts 1.plus_one
rescue NoMethodError => e
  puts e.message  # Expected: undefined method `plus_one'
end

# Inside refinement scope
RefinementTest.new

Output

undefined method `plus_one' for an instance of Integer
2
31

The methods are not accessible if the refined module is not used, indicating the narrow scope.

You can also read What Are The XML And RSS Processing In Ruby With Examples

Comparing Refined Scope vs. Unrefined Scope

A basic method such as String#reverse can be modified to provide a more lucid example of scope limitation.

Code Example 3: Modifying String#reverse

The adjustment that follows modifies String#reverse output to “Hell riders“:

module RefiningString
  refine String do
    def reverse
      "Hell riders"
    end
  end
end

class AClassWithoutMP
  def initialize(str)
    @str = str
  end

  def reverse
    @str.reverse  # Uses original String#reverse
  end
end

class AClassWithMP
  using RefiningString  # Refinement applied only in this class

  def initialize(str)
    @str = str
  end

  def reverse
    @str.reverse  # Uses refined String#reverse → "Hell riders"
  end
end

puts AClassWithoutMP.new("hello").reverse  # => "olleh"
puts AClassWithMP.new("hello").reverse     # => "Hell riders"

Output

olleh
Hell riders

You can also read Date And Time Manipulation In Ruby With Code Examples

Dual-Purpose and Dynamic Refinements

A module that contains refinements can be structured to be used for global monkey patching or limited-scope refinement, depending on how it is loaded. In development or testing situations, when global patching may be required, this is helpful.

Dual-Purpose Modules

Methods wrapped in refine (for scoped inclusion) and regularly declared methods (for global inclusion) can both be included in a module.

Code Example 4: Dual-Purpose Patching

module Patch
  # This method can be globally mixed into classes
  def patched?
    true
  end

  refine String do
    # refinement version of patched?
    def patched?
      true
    end
  end
end

# Global patch:
String.include Patch
puts "".patched?     # => true

# Refinement scope:
class LoadPatch
  using Patch

  def test
    "".patched?      # uses refined version
  end
end

puts LoadPatch.new.test   # => true

Output

true
true

Dynamic Refinements

Using is static and can only be called in a class or module definition. One intricate method to deal with load order problems (such as when refinement files are not loaded first) is to encapsulate the patched class/module definition in a Proc. You can use send :refine to dynamically invoke the refine method itself.

Code Example 5: Dynamic Refinement Activation

This example demonstrates how using can be called inside a loop and accept a variable referring to a module:

module Patch
  def patched?
    true
  end

  refine String do
    def patched?
      true
    end
  end
end

PATCH_CLASSES = [Patch]   # Use a constant instead of a local variable

class Patched
  PATCH_CLASSES.each { |klass| using klass }

  def test
    "".patched?
  end
end

puts Patched.new.test   # => true

Output

true

You can also read What Is Mean By Introspection And Singleton Classes In Ruby

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index