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

...