Integrating with Google Calendar in a Rails app - The Right Way
Tags: rails, ruby, webdev, react
Published: 2020-05-26 11:19:35

Part 1 of this series - [How NOT to integrate Google Calendar with your product](https://dev.to/gautamsawhney/how-to-not-integrate-google-calendar-with-your-product-45pi)

### <u> Prerequisite </u>
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`
```ruby
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](https://developers.google.com/calendar/v3/reference/events/list) is used for this. The gem provides an easier method called `list_events`

Ref code for `syncing events`
```ruby
  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.

```ruby
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](https://ngrok.com/).
`channel_id` - This is a UUID - `SecureRandom.uuid`


```ruby
  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`.

```ruby
  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.