Search code examples
rubyruby-on-rails-5

ruby when/how to use rescue/ensure?


I have a class to call geo location api. sample url below with Json response something like this

{ "location": { "lat": 31.0, "lng": 14.0 }, "accuracy": 112.4 }

I want to catch any errors, such as 503, 404 or any other errors that might be caused by error in request object. what is the proper way to this with rescue in ruby. also. if there is any error, can i return a message from the ensure block?

def findlocation
  begin
    location = httparty.get("https://www.googleapis.com/geolocation/v1/geolocate?key=#{API_KEY}")
  rescue 
    # do something after catching error
  ensure
    return { "message" : "unable to find location" }
  end 
end

Solution

  • In general exceptions should be used for exceptional events. Not regular application flow.

    When catching exceptions always be specific and only rescue exceptions that you know what to do with.

    begin 
      pats "hello world"
    rescue 
      nil
    end
    

    This example shows a serious flaw in the code in yout question - you created a black hole that swallows the NoMethodError that would have told us that there is a typo in the code. This makes debugging extremely difficult. This anti-pattern is known as Pokémon Exception Handling (Gotta catch em' all).

    ensure just ensures that the code is run no matter if the code raised an exception or not. Its used to for example guarantee that the method closes a file handler that it has opened or rolls back a transaction. Its a really big hammer that should be used very sparingly.

    HTTParty does not actually raise exceptions when the response code is a "error" code - because its not an exceptional event. Its a part of normal application flow when dealing with HTTP requests. Shit happens. HTTParty raises exceptions if you can't reach the server at all or you can't even reach the network. Those are exceptional events.

    class GeolocationClient
      include HTTParty 
      base_uri "https://www.googleapis.com/geolocation/v1"
      format :json
      attr_accessor :options
    
      def initialize(api_key:)
        @options = {
          api_key: api_key
        }
      end
    
      def geolocate
        begin 
          response = self.class.get("/geolocate", options)
          if response.successful?
            response 
          else
            logger.info("Geolocation API call was unsuccessful. Status code: #{response.code}")
            handle_unsuccessful_request
          end
        rescue HTTParty::Error => e 
          logger.warn(e.message)
          handle_unsuccessful_request
        end
      end
    
      private
    
      def handle_unsuccessful_request
        { "message" : "unable to find location" }
      end
    end
    

    response.successful? tests if the response is in the 2xx "happy range". Use a switch statement if you want handle each code or range of codes separately.