Exact matching against state lifecycle attributes can be a smell

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the algorithms category.

Last Updated: 2021-05-16

I had the following code for use in accounting. Basically it searched the PayPal API for the corresponding sales record with the state completed.

def completed_paypal_sale
  paypal_rest_sales.find do |sale|
    sale.state == 'completed'
  end
end

This covered 99% of use-cases, but returned nil sporadically.

The issue is a divergence between the semantics as a human understood them (either something is sold or not) and the limitations of this representation in a single attribute (which allows for states such as refunded or partially_refunded - both of which are not equal to completed therefore don't count as sales according to this logic - yet semantically are sales.)

Thus I should have written like this:

def completed_paypal_sale
  paypal_rest_sales.find do |sale|
    %w[completed refunded partially_refunded].include?(sale.state)
  end
end

Lesson

In general, when matching against some state/status attribute, you often need to think in terms of ordinals. Exact matching on a single state is a smell; instead you want to be checking for inclusion in all states beyond a certain point. State machine libraries help here.