Non-tech founder’s guide to choosing the right software development partner Download Ebook
Home>Blog>Rubyists-life-made-easier-with-composition-operators

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!

Do you have an idea? Launch it with us!
By submitting this form, you agree with JetRockets’ Privacy Policy