Alternative to Ruby's Monkey Patching

Rails Designer - Feb 26 - - Dev Community

This article was originally published on Rails Designer's Build a SaaS with Ruby on Rails


One of the joys of working with Ruby is it allows you to write code almost however you want, one feature that allows you to this is “monkey patching”: modifying methods in any class at runtime. This is truly a sharp knife: you might know where and how you use it, but third-parties, like gems, are not aware of your patches.

In this article, I want to highlight a better way to it instead: refinements.

Refinements was introduced in Ruby 2.0. It allows you to refine a class or module locally. Let's see an example (from the Ruby docs):

class C
  def foo
    puts "C#foo"
  end
end

module M
  refine C do
    def foo
      puts "C#foo in M"
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Alright, you define a module (M) and the refine the class you want (C).

c = C.new
c.foo # => prints "C#foo"

using M

c = C.new
c.foo # => prints "C#foo in M"
Enter fullscreen mode Exit fullscreen mode

Let's look at some real life examples from my own apps. I put these refinements in the lib folder as that's where I add all classes/modules that can be copied from app to app without modifications.

# lib/hash_dot.rb
module HashDot
  refine Hash do
    def to_dot
      JSON.parse to_json, object_class: OpenStruct
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Then wherever you want to use it, add to_dot to the hash:

class SubscriptionsController < ApplicationController
  using HashDot

  def create
    Subscription.create\
      name: data.plan_type,\ # instead of data.dig(:plan_type)
      status: data.status # instead of data.dig(:status)
  end

  private

  def data
    {
      plan_type: "professional",
      status: "active",
      billing_cycle: "monthly",
      amount_in_cents: 2999,
      features: ["unlimited_storage", "team_collaboration", "api_access"],
      seats: 5,
      next_billing_at: 1740369856,
      trial_ends_at: nil,
      created_at: 1732421070
    }.to_dot
  end
end
Enter fullscreen mode Exit fullscreen mode

Another example:

# lib/to_timestamp.rb
module ToTimestamp
  refine Integer do
    def to_timestamp
      Time.at(self)
    end
  end

  refine NilClass do
    def to_timestamp
      nil
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

The data in the SubscriptionsController uses UNIX timestamps (like Stripe does), let's use the refinement too, by calling to_timestamp on next_billing_at:

class SubscriptionsController < ApplicationController
  using HashDot
  using ToTimestamp

  def create
    Subscription.create\
      name: data.plan_type,\
      status: data.status,\
      next_billing_at: data.next_billing_at.to_timestamp
  end

  private

  def data
    {
      plan_type: "professional",
      status: "active",
      billing_cycle: "monthly",
      amount_in_cents: 2999,
      features: ["unlimited_storage", "team_collaboration", "api_access"],
      seats: 5,
      next_billing_at: 1740369856,
      trial_ends_at: nil,
      created_at: 1732421070
    }.to_dot
  end
end
Enter fullscreen mode Exit fullscreen mode

When calling to_timestamp it will convert it using Time.at:

data.next_billing_at.to_timestamp # => 2025-02-24 12:00:00 +0000
Enter fullscreen mode Exit fullscreen mode

And that are some examples of using refinements. Got more suggestions or use cases? Let me know below. 👇

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .