Be careful with attributes that are only available during parts of the lifecycle of an object

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

Last Updated: 2024-04-17

I had the following code fail due to coordinates being nil when I didn't expect them to be:

class Tutor
  geocode_by :postcode # this adds latitude and longitude attributes
  before_save :set_town

  def set_town
    self.town = Geocoder.search([latitude, longitude]).first.city
  end
end

The issue was that because set_town got called before the geocode_by generated the latitude and longitude coordinates. I had made a false assumption about when data was available.

Lesson

When accessing any attribute of a model that has state that only gets generated during certain stages of its lifecycle, ask yourself if the state is definitely going to available at this point.

And more generally, these kinds of mistakes are all too easy to make. It's worth thinking of ways to make the triggering dependent on that state becoming present (i.e. latitude being set here) rather than simply waiting for a save event.

For example

class Tutor
  geocode_by :postcode # this adds latitude and longitude attributes
  after_save :set_town

  def set_town
    if latitude_changed?
      self.town = Geocoder.search([latitude, longitude]).first.city
    end
  end
end