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!