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
10: Blocks, Procs, and Lambdas
I was looking through some old code recently and realized I had this massive case statement that handled different types of pricing discounts. It was a nightmare to maintain. Every time the marketing team wanted a "Summer Sizzler" or a "Winter Warmup" discount, I had to open the core pricing class and add another branch. It felt wrong. In Ruby, we have a much better way to pass logic around as if it were data.
Passing behavior with blocks
Let's try to solve this. I want a method that handles the "boilerplate" of a price calculation—maybe logging the transaction or checking if the item is in stock—but I want the actual discount logic to be flexible. I'll start with a block.
def apply_discount(price)
puts "Processing price: #{price}..."
# I'll use yield to "jump" into the block provided
result = yield(price)
puts "New price: #{result}"
result
end
# Now I can define the logic on the fly
apply_discount(100) { |p| p * 0.90 } # 10% off
apply_discount(100) { |p| p - 15 } # $15 flat discount
This is great for one-offs. The yield keyword is the magic here; it tells Ruby, "Pause this method and go run that chunk of code between the do...end or {...}." But here's the problem: if I have a "Loyalty Member" discount that I use in ten different places in my app, I'm tired of typing { |p| p * 0.85 } over and over. I want to save that logic to a variable.
Saving logic into Procs
This is where Proc (short for procedure) comes in. A Proc is basically a block that has been "captured" into an object. Let's try to move that loyalty discount into its own variable.
loyalty_discount = Proc.new { |p| p * 0.85 }
# Wait, if I just pass it like this, it won't work:
# apply_discount(100, loyalty_discount)
# That would throw an ArgumentError because apply_discount takes one arg, not two.
# I have to use the '&' operator to tell Ruby "treat this Proc as a block"
apply_discount(100, &loyalty_discount)
The & is a bit of Ruby syntactic sugar. It converts the Proc object into a block that yield can use. Now I can have a library of discounts—seasonal_proc, clearance_proc—and just plug them in wherever I need.
The "Return" Trap and Lambdas
Now, here is where things get weird. I noticed something frustrating while debugging a Proc. Look at this:
def calculate_with_proc(price, discount_proc)
puts "Starting calculation..."
discount_proc.call(price)
puts "This line will NEVER run!"
price
end
my_proc = Proc.new { |p| return p * 0.5 } # Note the 'return'
calculate_with_proc(100, my_proc)
# Output:
# Starting calculation...
# (The method exits immediately!)
What happened? In a Proc, the return keyword doesn't just return from the Proc; it returns from the entire method that called the Proc. It's like a grenade that blows up the whole call stack. Usually, that's not what you want. This is why lambda exists. Lambdas are "stricter" Procs.
Let's try the exact same thing with a lambda:
def calculate_with_lambda(price, discount_lambda)
puts "Starting calculation..."
discount_lambda.call(price)
puts "This line DOES run!"
price
end
my_lambda = ->(p) { return p * 0.5 } # Using the 'stabby lambda' syntax
calculate_with_lambda(100, my_lambda)
# Output:
# Starting calculation...
# This line DOES run!
The lambda is more polite. Its return only exits the lambda itself, returning control back to the method. Also, lambdas are picky about arguments. If you pass two arguments to a lambda that expects one, it'll throw an error. A Proc? It'll just ignore the extra argument and keep chugging along. Personally, I almost always use lambdas because the stricter behavior makes debugging way easier.
📋 Practical Task
Build a Dynamic Order Total Engine
You are building a checkout system where taxes and shipping fees vary by region. Instead of writing a dozen if/else statements, you will use a combination of Procs and Lambdas to calculate the final total.
Requirements:
- Create a method called
calculate_final_pricethat takes abase_priceand two arguments: atax_calculatorand ashipping_calculator. - Inside
calculate_final_price, use the.callmethod to apply the tax first, and then apply the shipping fee to that result. - Define a lambda for "NY State Tax" (8.875%) and a lambda for "CA State Tax" (7.25%).
- Define a Proc for "Standard Shipping" (flat $5.00) and a Proc for "Express Shipping" (flat $15.00).
- Execute the
calculate_final_pricemethod at least twice using different combinations of these lambdas and procs (e.g., NY Tax with Express Shipping) and print the results to the console.
There are no comments for now.