ruby-on-railsrubyif-statementerror-handlingnull

Best practices for handling multiple nil/invalid checks without throwing an exception


The following ruby method is expected to return either an array or nil, depending on whether or not the api call was successful.

  # @return [Array or nil] - but should not throw error if anything fails
  def fetch_coords_from_api
    api_url = "some_api_url"
    params = { api_key: ENV["API_KEY"] }
    response = ApiClient.new(api_url, params).get
    return unless response
    json = JSON.parse(response.body)
    return unless json.is_a?(Hash) && json["result"]
    addresses = json["result"]
    return unless addresses.is_a?(Array) && !addresses.empty?
    address = addresses.find { |add| add["zip"] == zip }
    return unless address&.key?("latitude") && address&.key?("longitude")
    [ address["latitude"], address["longitude"] ]
  end

Some context: the method is part of a class that attempts several methods of retrieving coordinates (if one method fails we move on to the next).

This works as intended but also seems like it violates several best practices and conventions (regarding the multiple checks for an invalid state). Can anyone suggest a more robust/efficient/conventional/pretty way of handling a scenario like this?


Solution

  • While I am still unclear on the implementation here is how I would go about refactoring the code you have now to avoid all the early returns

    def fetch_coords_from_api
      api_url = "some_api_url"
      params = { api_key: ENV["API_KEY"] }
      if response = ApiClient.new(api_url, params).get
        json = JSON.parse(response.body, {symbolize_names: true})
        #please note the local variable `zip` does not exist in the current context
        case json 
        in result: [*,{latitude: , longitude: , zip: ^zip, **},*]
          [latitude,longitude]
        else 
          nil 
        end  
      end
    end
    

    This uses pattern matching to find the desired entry by asserting the specified structure and pinning the desired zip code. If it exists this will return the latitude and longitude as an Array otherwise it will return nil.