DEV Community

Eugene Komissarov
Eugene Komissarov

Posted on • Originally published at jetrockets.pro

4 1

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!

AWS Q Developer image

Your AI Code Assistant

Ask anything about your entire project, code and get answers and even architecture diagrams. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Start free in your IDE

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay