Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

This document describes how to setup synchronization between a Rails application and Google Calendar. I am using the Google Calendar API v3 with OAuth2 and a service account to programmatically access and manage the calendar data.

...

Setup your rails environment

  1. Install the gem 'google-api-client'
  2. Copy private key of the service account to your local folder a folder outside your app/ and set proper permissions
  3. In your ruby code, the parameters you pass should contain calendarId (primary) and person (the email address of the calendar user)
  4. See EventR for a working code sample

  5. Our app EventR uses this mechanism, here is the relevant code (in the form of a ActiveSupprt::Concern module and using Rails 4.1 config/secrets.yml)

Code Block
themeMidnight
languageruby
titleconfig/secrets.yml
development: &development
  calendarId: 'your calendar ID here. This can most of the times be set to: primary'
  service_account_email: 'xxx@developer.gserviceaccount.com'
  impersonate_user_email: 'replaceWithEmailYouWantToImpersonate@email.com'
  key_file: <%= "#{ENV['HOME']}/.eventr/<your key here>" %>
  key_secret: 'private-key-secret'

test:
  <<: *development

integration:
  <<: *development

production:
  <<: *development


Code Block
themeMidnight
languageruby
titleapp/models/concerns/calendar.rb
module Calendar
  require 'google/api_client'
  extend ActiveSupport::Concern

  API_VERSION = 'v3'
  CACHED_API_FILE = "calendar-#{API_VERSION}.cache"
  CALENDAR_ID = Rails.application.secrets.calendarId

  def gcal_event_insert
    params = {
      calendarId: CALENDAR_ID
    }
    result = client.execute(
      :api_method => calendar.events.insert,
      :parameters => params,
      :body_object => convert_to_gcal_event
    )
    logger.debug(result.data.to_yaml)
    result
  end

  def gcal_event_update
    params = {
      calendarId: CALENDAR_ID,
      eventId: self.gcal_id
    }
    result = client.execute(
      :api_method => calendar.events.update,
      :parameters => params,
      :body_object => convert_to_gcal_event
    )
    logger.debug(result.data.to_yaml)
  end

  def gcal_event_delete
    params = {
      calendarId: CALENDAR_ID,
      eventId: self.gcal_id
    }
    result = client.execute(
      :api_method => calendar.events.delete,
      :parameters => params
    )
    logger.debug(result.data.to_yaml)
  end

private
  def convert_to_gcal_event
    event = {
      'summary' => self.name,
      'description' => self.description,
      'start' => {
         'dateTime' => self.tstart
      },
      'end' => {
         'dateTime' => self.tend
      },
      'location' => get_event_location,
      'extendedProperties' => {
        'private' => {
          'id' => self.id
        }
      }
    }
  end

  def get_event_location
    [self.location.try(:name),
      self.location.try(:address),
      self.location.try(:city),
      self.location.try(:country)].compact.join(", ")
  end

  def init_client
    @client = Google::APIClient.new(:application_name => 'EventR', :application_version => '1.0.0')

    # Load our credentials for the service account
    key = Google::APIClient::KeyUtils.load_from_pkcs12(Rails.application.secrets.key_file, Rails.application.secrets.key_secret)
    @client.authorization = Signet::OAuth2::Client.new(
      :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
      :audience => 'https://accounts.google.com/o/oauth2/token',
      :scope => 'https://www.googleapis.com/auth/calendar',
      :issuer => Rails.application.secrets.service_account_email,
      :person => Rails.application.secrets.impersonate_user_email,
      :signing_key => key)

    # Request a token for our service account
    @client.authorization.fetch_access_token!
    @client
  end

  def init_calendar
    @calendar = nil
    # Load cached discovered API, if it exists. This prevents retrieving the
    # discovery document on every run, saving a round-trip to the discovery service.
    if File.exists? CACHED_API_FILE
      File.open(CACHED_API_FILE) do |file|
        @calendar = Marshal.load(file)
      end
    else
      @calendar = @client.discovered_api('calendar', API_VERSION)
      File.open(CACHED_API_FILE, 'w') do |file|
        Marshal.dump(@calendar, file)
      end
    end
  end

  def client
    @client ||= init_client
  end

  def calendar
    @calendar ||= init_calendar
  end

end