Rubyists life made easier with composition operators.

enoch0x5a

Eugene Komissarov

Posted on August 23, 2019

Rubyists life made easier with composition operators.

If you write Ruby code and wandered into FP world you might just started writing those little tiny methods inside your classes/modules. And that was awesome to write code like this:

class Import
  # Some code goes here...

  def find_record(row)
    [ Msa.find_or_initialize_by(region_name: row[:region_name], city: row[:city], state: row[:state], metro: row[:metro] ), row ]
  end

  # record is one of:
  # Object - when record was found
  # false - when record was not found
  def update_record(record, attributes)
    record.attributes = attributes
    record
  end

  # record is one of:
  # false
  # ZipRegion
  def validate_record(record)
    case record
    when false
      [:error, nil]
    else
      validate_record!(record)
    end
  end

  # record is ZipRegion object
  def validate_record!(record)
    if record.valid?
      [:ok, record]
    else
      error(record.id, record.errors.messages)
      [:error, record]
    end
  end

  def persist_record!(validation, record)
    case validation
    when :ok
      record.save
    when :error
      false
    end
  end
end

Yeah, I know there is YARD, and argument types are somewhat weird but at the time of coding, I was fascinated with Gregor Kiczales's HTDP courses (that was a ton of fun, sincerely recommend for every adventurous soul).

And next comes dreadful composition:

def process(row, index)
    return if header_row?(row)

    success(row[:region_name], persist_record!(*validate_record(update_record(*find_record(parse_row(row))))))
  end

The pipeline is quite short but already hard to read. Luckily, in Ruby 2.6 we now have 2 composition operators: Proc#>> and its reverse sibling Proc#<<.

And, with a bit of refactoring composition method becomes:

def process(row, index)
    return if header_row?(row)

    composition = method(:parse_row)        >>
                  method(:find_record)      >>
                  method(:update_record)    >>
                  method(:validate_record)  >>
                  method(:persist_record!)  >>
                  method(:success).curry[row[:region_name]]

    composition.call(row)

Much nicier, don't you think? Ruby just became one step closer to FP-friendly languages family, let's hope there'll be more!

💖 💪 🙅 🚩
enoch0x5a
Eugene Komissarov

Posted on August 23, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related