Skip to Content
Course content

50: Functional-Style Method Chaining

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

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:

  1. Start with the logs array.
  2. Chain compact to remove the nils.
  3. Chain select to keep only entries where the action is "login".
  4. Chain map to extract just the username.
  5. Chain uniq to remove duplicate users.
  6. Chain map again to convert those usernames to uppercase.
  7. Chain sort to 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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.