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. 👇
Top comments (0)