Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're working in a huge, shared office kitchen. You find that the coffee machine is missing a crucial adapter for your favorite travel mug. If you were to "monkey patch" the machine—meaning you permanently glue that adapter onto the machine—everyone else in the office is now forced to deal with your adapter. If someone else needs a different adapter, they might rip yours off or glue their own on top of yours. It's chaos. You've changed the global environment for everyone, and you have no idea who else is relying on how that machine behaves.

Refinements are like bringing your own personal adapter to the kitchen. You plug it in, use it to get your coffee, and then put it back in your bag when you're done. For the few minutes you're standing there, the machine works exactly how you need it to, but for everyone else in the office, the machine remains completely untouched. No one is surprised, and nothing breaks unexpectedly.

The Chaos of Global Modification

In Ruby, we're used to the power of opening any class and adding methods. It's great for small scripts, but in a professional codebase, it's a liability. If I add a to_slug method to the String class in one part of a massive app, and you add a different to_slug method in another part, the last one loaded wins. We call this "polluting the global namespace," and it's a leading cause of those "but it worked on my machine" bugs that keep us up until 2 AM.

Scoped Power with Refinements

Refinements allow us to extend a class, but restrict that extension to a specific scope. I usually use them when I need a specialized helper method that only makes sense within one specific service or controller.

module StringExtensions
  refine String do
    def to_slug
      self.downcase.strip.gsub(/\s+/, '-')
    end
  end
end

class BlogPost
  # This is where the magic happens. 
  # The refinement is active ONLY inside this class.
  using StringExtensions

  def initialize(title)
    @title = title
  end

  def url_path
    "/posts/#{@title.to_slug}"
  end
end

post = BlogPost.new("Hello Ruby World")
puts post.url_path # => "/posts/hello-ruby-world"

# Now, look what happens outside the class:
puts "Hello Ruby World".to_slug 
# NoMethodError: undefined method `to_slug' for "Hello Ruby World":String

Where the Boundaries Are Drawn

I want to point out a few things about how using works, because it's a bit more restrictive than standard Ruby inheritance. You can't just call using anywhere. It has to be at the top level of a file or inside a class or module definition.

One thing that trips people up is that refinements are lexically scoped. This means they only apply to the code that physically follows the using statement in that file. If you call a method from another class that doesn't have the using statement, the refinement won't follow you there. I actually love this limitation—it forces you to be explicit about where you're modifying core behavior, which makes the code much easier to audit later.

Use refinements when you're thinking, "I really wish Integer had this one specific method for this one specific calculation," but you don't want to risk breaking every other gem in your bundle that also uses Integer.




📋 Practical Task

Building a Scoped Currency Formatter

You are working on a financial reporting tool. You need a way to format numbers as currency strings (e.g., 1234.5 becomes "$1,234.50"), but you only want this functionality available inside the InvoiceGenerator class to avoid confusing other developers who might be doing pure math elsewhere in the app.

Your Goal:

  • Create a module named NumericFormatting.
  • Inside that module, create a refinement for the Numeric class.
  • Add a method called to_currency to the refinement that returns the number formatted as a string with a leading "$" and two decimal places (Hint: you can use sprintf or "%.2f" % self).
  • Create a class InvoiceGenerator that uses the NumericFormatting refinement.
  • Inside InvoiceGenerator, write a method print_total(amount) that prints the amount using your new to_currency method.
  • Verify that calling to_currency on a number outside of the InvoiceGenerator class raises a NoMethodError.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.