Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
21: Refinements
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
Numericclass. - Add a method called
to_currencyto the refinement that returns the number formatted as a string with a leading "$" and two decimal places (Hint: you can usesprintfor"%.2f" % self). - Create a class
InvoiceGeneratorthat uses theNumericFormattingrefinement. - Inside
InvoiceGenerator, write a methodprint_total(amount)that prints the amount using your newto_currencymethod. - Verify that calling
to_currencyon a number outside of theInvoiceGeneratorclass raises aNoMethodError.
There are no comments for now.