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
50: Functional-Style Method Chaining
Up until now, we've spent a lot of time talking about how to change the state of objects. But in a lot of professional Ruby codebases—especially those influenced by functional programming—you'll see a pattern where we avoid changing the original data entirely. Instead, we create a "pipeline" where data flows through a series of methods, each returning a new version of the collection.
This is functional-style method chaining. The key is using methods that return a new array or collection (like select or map) rather than their destructive counterparts (like select! or map!). Let's build a small data processor for a fake e-commerce order feed to see this in action.
Setting the Stage with Raw Order Data
Imagine we're getting a messy array of order hashes from an external API. Some entries are nil because of transmission errors, some are cancelled, and the prices are just raw numbers. We want a clean, sorted list of formatted strings for a daily report.
orders = [
{ id: 1, status: "shipped", total: 45.0 },
nil,
{ id: 2, status: "cancelled", total: 20.0 },
{ id: 3, status: "shipped", total: 120.5 },
nil,
{ id: 4, status: "shipped", total: 15.75 }
]
Building the Transformation Pipeline
I want to process this list in one fluid motion. Instead of creating five different temporary variables like filtered_orders or sorted_orders, I'm going to chain the methods. First, I'll strip the nils, then filter for only the "shipped" orders, and then calculate a 10% tax on each.
processed_orders = orders
.compact
.select { |order| order[:status] == "shipped" }
.map { |order| order[:total] * 1.10 }
# Result: [49.5, 132.55, 17.325]
Notice how each method returns a new array, which the next method then operates on. It's a conveyor belt of data.
The "Nil" Trap
Here is where I usually trip up when I'm rushing. I want to find the single most expensive order in this list and then format it. I'll try to tack a max call onto the end of my chain, and then immediately call map to turn it into a string.
# This is where I made a mistake
final_report = orders
.compact
.select { |order| order[:status] == "shipped" }
.map { |order| order[:total] * 1.10 }
.max
.map { |price| "$#{price.round(2)}" }
If you ran this, Ruby would throw a NoMethodError: undefined method 'map' for Float. Why? Because .max doesn't return an array; it returns a single number (the maximum value). I broke the chain. I was treating a single value as if it were still a collection.
To fix this, I have to decide if I want a list of one item or just a single formatted string. Since it's a report, I'll just move the formatting to the very end, after the chain has finished producing the single value.
Polishing the Final Output
Let's put it all together. I'll keep the chain focused on the collection transformations, and then I'll handle the final scalar value at the end. I'll also add a sort step just to show how we can reorder the data mid-stream.
# The corrected, complete pipeline
top_order_value = orders
.compact
.select { |o| o[:status] == "shipped" }
.map { |o| o[:total] * 1.10 }
.sort
.last
# Now I format the result outside the chain
puts "The highest taxed order was: $#{top_order_value.round(2)}"
# Output: The highest taxed order was: $132.55
By keeping the logic separate—transforming the collection first and then extracting the specific value—the code remains readable and doesn't crash. This style is incredibly powerful when you're dealing with complex data transformations; it reads like a recipe: "do this, then this, then that."
📋 Practical Task
Refactoring the User Activity Log
You have a raw log of user activities. Your goal is to create a functional chain that processes this data into a sorted list of unique usernames who performed a "login" action, formatted in uppercase.
The Data:
logs = [
{ user: "alice", action: "login" },
{ user: "bob", action: "logout" },
{ user: "alice", action: "login" },
nil,
{ user: "charlie", action: "login" },
{ user: "bob", action: "login" },
nil
]
Your Task:
- Start with the
logsarray. - Chain
compactto remove the nils. - Chain
selectto keep only entries where the action is "login". - Chain
mapto extract just the username. - Chain
uniqto remove duplicate users. - Chain
mapagain to convert those usernames to uppercase. - Chain
sortto put them in alphabetical order.
Write the complete chain and print the final result to the console. Ensure you do not use any destructive methods (like uniq!) to keep the pipeline functional.
There are no comments for now.