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!
Top comments (0)