Search code examples
ruby-on-railsrubynomethoderror

rails nomethoderror only in production


I have a rails app with google calendar API integrated. App is deployed on heroku. The following code is working in localhost but I getting nomethoderror in production as shown below. Why is this happening?

errors for get_own_events:

Attempting refresh of access token & retry of request
2016-02-10T13:24:34.161850+00:00 app[web.1]: 
2016-02-10T13:24:34.161853+00:00 app[web.1]: NoMethodError (undefined method `each' for nil:NilClass):
2016-02-10T13:24:34.161854+00:00 app[web.1]:   lib/google_calendar_api.rb:91:in `get_own_events'
2016-02-10T13:24:34.161855+00:00 app[web.1]:   app/controllers/events_controller.rb:18:in `index'

errors for get busy events:

2016-02-10T13:24:00.716112+00:00 app[web.1]: Attempting refresh of access token & retry of request
2016-02-10T13:24:00.779120+00:00 app[web.1]: NoMethodError (undefined method `[]' for nil:NilClass):
2016-02-10T13:24:00.779121+00:00 app[web.1]:   lib/google_calendar_api.rb:38:in `get_busy_events'
2016-02-10T13:24:00.779121+00:00 app[web.1]:   app/controllers/events_controller.rb:20:in `index'

events_controller

  if params['start'] && params['end']
    @start_time = params['start'].to_datetime.rfc3339
    @end_time = params['end'].to_datetime.rfc3339
    @timezone = params['timezoneParam']
    if @user == @current_user
      @results = get_own_events(@google, @start_time, @end_time, @timezone)
    else
      @results = get_busy_events(@google, @start_time, @end_time, @timezone)
    end
  end

get_own_events method:

def get_own_events(social_object, start_time, end_time, timezone)
  client = init_google_api_calendar_client(social_object)
  old_token = client.authorization.access_token
  service = client.discovered_api('calendar', 'v3')

  #API request/response
  result_raw = client.execute(
    :api_method => service.events.list,
    :parameters => { 'calendarId' => social_object.email,
                   'timeMin' => start_time,
                   'timeMax' => end_time,
                   'timeZone' => timezone },
    :headers => {'Content-Type' => 'application/json'})

  #token refresh if needed
  new_token = client.authorization.access_token
  if old_token != new_token
    social_object.update_attribute(:token, new_token)
    get_own_events(social_object, start_time, end_time, timezone)
  end

  #API response parsing
  result = JSON.parse(result_raw.body)['items']
  #result_timezone = JSON.parse(result_raw.body)['timeZone']

  #changing response to fullcalendar format
  formatted_event_array = []

  result.each do |event| #THIS IS LINE 91
    start_time = event['start']['dateTime'].to_datetime.rfc822
    end_time = event['end']['dateTime'].to_datetime.rfc822
    #timezone = result_timezone
    all_day = false
    title = event['summary']
    formatted_event = {}
    formatted_event['title'] = title
    formatted_event['start'] = start_time
    formatted_event['end'] = end_time
    formatted_event['allDay'] = all_day
    formatted_event_array << formatted_event
  end

  return formatted_event_array
end

get_busy_events method:

def get_busy_events(social_object, start_time, end_time, timezone)
  client = init_google_api_calendar_client(social_object)
  old_token = client.authorization.access_token
  service = client.discovered_api('calendar', 'v3')

  #API request/response
  result_raw = client.execute(
    :api_method => service.freebusy.query,
    :body => JSON.dump({ 
            :timeMin => start_time,
            :timeMax => end_time,
            :timeZone => timezone,
            :items => [{ :id => social_object.email }]}),
    :headers => {'Content-Type' => 'application/json'})

  #token refresh if needed
  new_token = client.authorization.access_token
  if old_token != new_token
    social_object.update_attribute(:token, new_token)
    get_busy_events(social_object,start_time, end_time, timezone)
  end

  #API response parsing 
  result = JSON.parse(result_raw.body)['calendars'][social_object.email]['busy'] #THIS IS LINE 38

  #changing response to fullcalendar format
  formatted_event_array = []

  result.each do |event|
    start_time = event['start'].to_datetime.rfc822
    end_time = event['end'].to_datetime.rfc822
    formatted_event = {}
    formatted_event['title'] = 'busy'
    formatted_event['start'] = start_time
    formatted_event['end'] = end_time
    #no allDay in freebusy response, so time difference must be checked
    if event['start'].to_datetime + 1.day <= event['end'].to_datetime
      formatted_event['allDay'] = true
    else
      formatted_event['allDay'] = false
    end
    formatted_event_array << formatted_event 
  end

  return formatted_event_array
end

Solution

  • Effectively it's this

    result = JSON.parse(result_raw.body)['calendars'][social_object.email]['busy']
    

    If JSON.parse(result_raw.body)['calendars'][social_object.email]['busy'] is nil then this

    result.each do |event|
    # code
    end
    

    Is attempting to iterate over nil.

    You could fix that with a guard clause

    return formatted_event_array if result.nil?
    result.each do |event|
    # code
    end
    

    But to me that's papering over the cracks somewhat. The other error is because of that same line.

    Assuming parsed_body = JSON.parse(result_raw.body)

    By chaining the hash calls without checking it'll throw your error if parsed_body['calendars'] is nil or if parsed_body['calendars'][social_object.email] is nil

    So effectively you want to check if those both exist, and that parsed_body['calendars'][social_object.email]['busy'] exists. If they don't then presumably return the empty array

    So

    if parsed_body['calendars'] && parsed_body['calendars'][social_object.email] && parsed_body['calendars'][social_object.email]['busy']