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
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 below. 👇