Integrating with Google Calendar in a Rails app - The Right Way

gautamsawhney

Gautam Sawhney

Posted on May 26, 2020

Integrating with Google Calendar in a Rails app - The Right Way

Part 1 of this series - How NOT to integrate Google Calendar with your product

Prerequisite

1) If using rails then use the gem google-api-client
2) I am considering here that you already have the access_token of the user. I will write a different blog to explain how to get that.

1) Do full initial synch of events

It has the following steps -

  • Fetch a new access_token if the token has expired.
  • Create the service authorization object which will be used for fetching the events.

Ref code for service authorization

def create_service_auth
  #create service auth
  @service = Google::Apis::CalendarV3::CalendarService.new
  @service.authorization = token.google_secret.to_authorization
  return unless token.expired?

  new_access_token = @service.authorization.refresh! #refresh access_token
end
  • Fetching all calendar events(past, present and future).

    • The full sync is the original request for all the resources of the collection you want to synchronize.
    • In the response to the list operation, you will find a field called nextSyncToken representing a sync token. You'll need to store the value of nextSyncToken. If the result set is too large and the response gets paginated, then the nextSyncToken field is present only on the very last page.
    • Depending on what your use case is, it will be better to perform this job as a background task.
    • Events: list API is used for this. The gem provides an easier method called list_events

Ref code for syncing events

  def get_events
    @events_arr = []
    @events_list = @service.list_events('primary', single_events: true, max_results: 500)
    @sync_token = @events_list.next_sync_token
    @page_token = @events_list.next_page_token
    @events_arr << @events_list.items
    while @sync_token.blank?
      @events_list = @service.list_events('primary', single_events: true, max_results: 500, page_token: @page_token)
      @sync_token = @events_list.next_sync_token
      @page_token = @events_list.next_page_token
      @events_arr << @events_list.items
    end
  end

2) Create a webhook to receive push notifications

After a full sync of events, the next step is to setup a Webhook so that google can inform us of the changes that we subscribe for.
For every user that links their calendar to the app, we will create a subscription so that we can be informed whenever there is a change in their calendar.

It has the following steps -

  • Fetch a new access_token if the token has expired.
  • Create the service authorisation object which will be used for fetching the events, exactly same as shown above.
  • Set up a Channel - It creates a channel with google and specifies the callback URL or the web-hook URL.
  • Watch events - After the web-hook is set up, we need to specify what events we want to watch and also need to specify from which calendar.
def setup_channel
  @channel = Google::Apis::CalendarV3::Channel.new(address: callback_url, id: channel_id, type: "web_hook")
end

callback_url - It can't be localhost, it has to be a valid https url. For testing purposes you can use ngrok.
channel_id - This is a UUID - SecureRandom.uuid

  def watch_events
    time_min = DateTime.now.rfc3339
    @webhook = @service.watch_event('primary', @channel, single_events: true, time_min: time_min)
  end

primary - refers to the primary calendar of the user.
single_events - Setting it to true also gives all events belonging to 1 single recurring event.

Now, whenever there will be any change in the primary calendar of the user google will hit the registered web-hook for the user.

In the request Google will pass X-Goog-Resource-ID and X-Goog-Channel-ID. We would have to hit the list_events API again to fetch the changed events data for that user.

Only difference will be that instead of passing the page token like we did earlier, we would pass the sync_token.

  def get_events
    @events_list = @service.list_events('primary', single_events: true, max_results: 2500, sync_token: sync_token)
  end

3) Saving X-Goog-Resource-ID & X-Goog-Channel-ID

When we created the web-hook google will return us with a resource_id, resource_uri, id(that we created). We need to save all this data so that we can get to know for which user the events have changed.
Also the channel expires in around 1 week so we need to keep creating new web-hooks before it expires.

4) Deleting the events with status cancelled

This is the flow that took me some time to understand. So what happens when a user changes the time of their event or has the user changed a single event or all the events in a recurring event. What google does is that

  • if the user changes a single event, then google keeps the calendar_id as same.
  • if the user changes a recurring event and selects all or following events as option then the calendar_id changes for all the events. Hence, in this case we need to delete the old events and add new events in our system. So, this is a check that you will have to add when saving the calendar events in your system.

That's it - It's quite messy if you are trying to figure it out from scratch and I hope this article will help you all.

If you face any issue, connect with me on twitter and I will be more than happy to help.

💖 💪 🙅 🚩
gautamsawhney
Gautam Sawhney

Posted on May 26, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related