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
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"
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
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
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
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
When calling to_timestamp
it will convert it using Time.at
:
data.next_billing_at.to_timestamp # => 2025-02-24 12:00:00 +0000
And that are some examples of using refinements. Got more suggestions or use cases? Let me know.