TeamSnap Logo
Log inFind My Team
  • Products
    • Organizations logoClubs & Leagues

      For clubs, leagues & associations with multiple teams

      • Sports
      • Features
      • Get a demo
    • Teams logoTeams

      The easiest way to manage a team

      • Sports
      • Features
    • Tournaments logoTournaments

      Build and manage your events in a snap

      • Scheduling
      • Registration
  • Pricing
  • Company
    • About
    • Careers Hiring!
  • Resources
    • Blog
    • Community Impact
    • Off-Season
    • Coaches’ Corner
    • Clubs & Leagues Resources
    • TeamSnap Updates
    • Help Center
    • API Docs
  • For Brands

Find My Team

Sign upLog in
Sign upLog in
  • Home
  • Ruby SDK
  • API
  • API Help / Feedback
Blog/ Ruby / Implementing pipe in Ruby - Part 2…

Implementing pipe in Ruby - Part 2

Image of engineering blog author: Dan MatthewsDan Matthews
  • Ruby
  • Unix

Last month, Shane Emmons did a post on implementing pipe in ruby. Check out his post here.

As Shane mentioned, the unix pipe operator is extremely powerful and simple to use / understand. The result of the first operation is passed as the argument for the next operation and so on. Last summer, shortly before I came onboard, Shane implemented our first version:

def pipe(initial_env, through:)
  through.reduce(initial_env) do |env, method|
    status, _, _ = env
    success?(status) ? send(method, env) : env
  end
end

As we progressed, changes were made and additional, slightly different, versions of the pipe method started creeping in.

Here they are as of the beginning of 2/16/2015:

def pipe(response, through:)
  through.reduce(response) { |resp, method|
    resp.success? ? send(method, resp) : resp
  }.to_ary
end
def pipe(response, through:)
  through.reduce(response) { |resp, method|
    resp.success? ? send(method, resp) : resp
  }
end
def pipe(message, through: [])
  through.reduce(message) { |msg, method|
    break unless msg.continue?

    begin
      send(method, msg)
    rescue => e
      handle_error(e, { method: method, message: msg })
    end
  }
end

Sure enough, as I started on my most recent task, I came across the need to write another slightly different version of the method. The code duplication got to me. Another problem that the whole team was encountering had to do with debugging and tracing errors. Ruby is normally very good at providing detailed information about what's going wrong. In this case, however, errors would occur in one of the methods specified in the through array and the stack trace would point back to the pipe method. We didn't know which method was failing or even what arguments were passed to it. Many of us found ourselves adding STDOUT.puts in each method called and debugging from there. For me at least, this brought back nightmarish memories of alert debugging JavaScript prior to the days of Firebug and Chrome's JS console.

In an attempt to rid myself of these nightmares, the pipe-ruby gem was born.

Now what?

As I looked at our implementations, there were a number of requirements popped out at me.

  • Our ability to report on errors needs to be decoupled from the actual raise
  • We should be able to gracefully exit the pipe at the beginning of each loop
  • Skipping one or more steps based on the current argument's value should be possible
  • Raised errors need to be helpful, pointing out the location of the error and the value of the most recently passed argument
  • It should be possible to conditionally break out of the pipe
  • Conditional skipping of one or more methods in the pipe should be possible

Additionally, I found that I would be iterating over an array and passing each value to pipe. It seemed easiest to add that to the GEM, so pipe_each was born.

The Pipe::Config object provides the fine tuning capabilities defined by our existing requirements.

Pipe::Config.new(
  :error_handlers => [],   # an array of procs to be called when an error occurs
  :raise_on_error => true, # tells Pipe to re-raise errors which occur
  :skip_on => false,       # a truthy value or proc which tells pipe to skip the
                           # next method in the `through` array
  :stop_on => false        # a truthy value or proc which tells pipe to stop
                           # processing and return the current value
)

There are a number of ways to send a custom config to #pipe or #pipe_each. They are fully described in the README's Configurable Options section. The README also does a great job of outlining error handling and skipping / stopping execution.

Products
Club or League Administrators
  • Get a demo
  • Features
  • Pricing
  • Tournaments
  • FAQs
Coaches or Team Managers
  • Features
  • Pricing
  • Tournaments
  • Get Started
Parents or Athletes
  • Features
  • Pricing
  • Find My Team
  • Get Started
Sports
Clubs & Leagues
  • Baseball
  • Basketball
  • Football
  • Ice Hockey
  • Lacrosse
  • Soccer
  • Softball
  • Volleyball
Teams
  • Baseball
  • Basketball
  • Football
  • Ice Hockey
  • Soccer
  • Softball
  • +100s more
Resources
  • Blog
  • Coaches’ Corner
  • Return To Play
  • Clubs & Leagues Resource Library
  • Skills & Drills
  • Press Releases
  • TeamSnap Updates
  • TeamSnap Help Center
  • Community
  • Why TeamSnap?
Company
  • About
  • CareersHiring!
  • For Brands
  • Partners
  • TeamSnap API
  • Responsible Disclosure Policy
  • Youth sports team and sports org app Apple App Store Logo
  • Youth sports team and sports org app Google Play Store Logo
  • social-media-facebook
  • social-media-twitter
  • social-pinterest
  • social-instagram
  • social-video-youtube
  • professional-network-linkedin

TeamSnap Footer Logo

Copyright © 2005–2023 TeamSnap, Inc.

  • Sitemap
  • Terms
  • Privacy
  • California Privacy
  • Do not sell my personal information – CA resident only