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…

Implementing pipe in Ruby

Image of engineering blog author: Shane EmmonsShane Emmons
  • Ruby
  • Unix

The unix | (pipe) operator is an extremely powerful and simple tool. With it you can do amazing feats, like kill vagrant when it's misbehaving:

ps aux | grep vagrant | grep -v grep | tr -s " " | cut -d" " -f2 | xargs kill

or open every file that references ShimMessageView:

vi $(ag ShimMessageView -l --nocolor | xargs)

Simply stated, the pipe takes input from the left, runs it through a command and outputs it on the right. Data in, data out. This pattern is so powerful, many languages, such as Clojure and Elixir, implement the pipe natively. Unfortunately Ruby does not.

While building our next generation API, I was searching for an way to make controller actions more transparent. Traditionally they're are a mess of state. Data is passed around behind the scenes making it very hard to follow the lifecycle of a request. That's when I thought "what about pipe?" It's easy, and when implemented using a functional, stateless style, should make our actions easy to follow. With that idea, the following method was born.

def pipe(response, through:)
  through.reduce(response) { |resp, method|
    resp.success? ? send(method, resp) : resp
  }.to_ary
end

Given a response object, pipe calls each method in the through array. If the result of the method is not successful (i.e. not a 2xx) then we break the pipe otherwise continue.

This allows us to write code like the following.

class MemberApp < BaseApp
  get "/members/:id" do |id|
    collection = MemberMapper.new(identity_map).find_by_ids(id)
    response = Response.new(:collection => collection)

    pipe(response, :through => [
      :not_found, :authorize_to_read, :apply_writable, :decorate, :serialize
    ])
  end

  post "/members" do
    pipe(default_response, :through => [
      :build, :validate, :authorize_to_write, :create, :apply_writable,
      :decorate, :serialize
    ])
  end
end

class BaseApp < Sinatra::Base
  def not_found(response)
    if response.empty_collection?
      response.with(:status => 404)
    else
      response
    end
  end
end

When is the last time you were able to look at a controller method in a Rails app and know exactly what each step was? I haven't been able to since the first commit after rails new.

I'm sure this article brings up some questions too. What's this Response object? How is the result of pipe translated into something Rack understands? What's an identity_map? I'll explain these concepts and more in future articles.

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