google-api 0.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ .DS_Store
4
+ .rvmrc
5
+ Gemfile.lock
6
+ coverage/
7
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format documentation
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## v0.0.1.alpha (2012-08-21) ##
2
+
3
+ * First release. (be careful!)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in google-api.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Alex Robbin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ **This gem is not yet released! Proceed at your own risk...**
2
+
3
+ Google API
4
+ ===================
5
+
6
+ A simple but powerful ruby API wrapper for Google's services.
7
+
8
+ Installation
9
+ -------
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'google-api'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install google-api
22
+
23
+
24
+ Before Using this Gem
25
+ -------
26
+
27
+ Google API depends on you to authenticate each user with Google via OAuth2. We don't really care how you get authenticated, but you must request access to a User's Google account and save the Refresh Token, Access Token, and Access Token Expiration Date.
28
+
29
+ As a starting point, we recommend using Omniauth for most authentication with Ruby. It is a great gem, and integrates seemlessly with most SSO sites, including Google. Check out the [Wiki](https://github.com/agrobbin/google-api/wiki/Using-Omniauth-for-Authentication) for more information.
30
+
31
+ Usage
32
+ -------
33
+
34
+ This gem couldn't be easier to set up. Once you have a Client ID and Secret from Google (check out the [Wiki](https://github.com/agrobbin/google-api/wiki/Getting-a-Client-ID-and-Secret-from-Google) for instructions on how to get these), you just need to add an initializer to your Rails application that looks like this:
35
+
36
+ ```ruby
37
+ GoogleAPI.configure do |config|
38
+ config.client_id = '[CLIENT ID]'
39
+ config.client_secret = '[Client SECRET]'
40
+ end
41
+ ```
42
+
43
+ We make it easy to set up an object as 'Google API Ready', giving you a couple of conveniences. For starters, if you have a User model, add the OAuth2 fields to your database migration:
44
+
45
+ ```ruby
46
+ create_table :users do |t|
47
+ t.string :email_address, :first_name, :last_name
48
+ t.oauthable
49
+ t.timestamps
50
+ end
51
+ ```
52
+
53
+ ...and methods to the Model:
54
+
55
+ ```ruby
56
+ class User < ActiveRecord::Base
57
+
58
+ oauthable
59
+
60
+ end
61
+ ```
62
+
63
+ Once you have set that up, making a request to the Google API is as simple as:
64
+
65
+ ```ruby
66
+ user = User.find(1)
67
+ client = GoogleAPI::Client.new(user)
68
+ client.drive.all
69
+ ```
70
+
71
+ This will fetch all files and folders in the user's Google Drive and return them in an array of hashes.
72
+
73
+ ## Contributing
74
+
75
+ 1. Fork it
76
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
77
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
78
+ 4. Push to the branch (`git push origin my-new-feature`)
79
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new('spec')
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |gem|
3
+ gem.authors = ["Alex Robbin"]
4
+ gem.email = ["alex@robbinsweb.biz"]
5
+ gem.summary = %q{A simple but powerful ruby API wrapper for Google's services.}
6
+ gem.description = gem.summary
7
+ gem.homepage = ""
8
+
9
+ gem.files = `git ls-files`.split($\)
10
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
11
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
12
+ gem.name = "google-api"
13
+ gem.require_paths = ["lib"]
14
+ gem.version = "0.0.1.alpha"
15
+
16
+ gem.add_runtime_dependency 'oauth2', '0.8.0'
17
+ gem.add_runtime_dependency 'rails', '>= 3.2'
18
+ gem.add_development_dependency 'bundler'
19
+ gem.add_development_dependency 'rake'
20
+ gem.add_development_dependency 'rspec'
21
+ gem.add_development_dependency 'simplecov'
22
+ end
@@ -0,0 +1,89 @@
1
+ # Adapted from Paperclip's implementation of available migration methods
2
+ # https://github.com/thoughtbot/paperclip/blob/v3.1.4/lib/paperclip/schema.rb
3
+
4
+ module GoogleAPI
5
+ module ActiveRecordInclusions
6
+
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ base.send :include, Migrations
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ def oauthable
15
+ define_method :oauth_hash do
16
+ {
17
+ access_token: oauth_access_token,
18
+ refresh_token: oauth_refresh_token,
19
+ expires_at: oauth_access_token_expires_at
20
+ }
21
+ end
22
+
23
+ define_method :update_access_token! do |access_token|
24
+ self.oauth_access_token = access_token
25
+ self.oauth_access_token_expires_at = 59.minutes.from_now
26
+ self.save
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ module Migrations
33
+
34
+ COLUMNS = {
35
+ oauth_refresh_token: :string,
36
+ oauth_access_token: :string,
37
+ oauth_access_token_expires_at: :datetime
38
+ }
39
+
40
+ def self.included(base)
41
+ ActiveRecord::ConnectionAdapters::Table.send :include, TableDefinition
42
+ ActiveRecord::ConnectionAdapters::TableDefinition.send :include, TableDefinition
43
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, Statements
44
+ ActiveRecord::Migration::CommandRecorder.send :include, CommandRecorder
45
+ end
46
+
47
+ module TableDefinition
48
+
49
+ def oauthable
50
+ COLUMNS.each do |name, type|
51
+ column(name, type)
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ module Statements
58
+
59
+ def add_oauthable
60
+ COLUMNS.each do |name, type|
61
+ add_column(name, type)
62
+ end
63
+ end
64
+
65
+ def remove_oauthable
66
+ COLUMNS.each do |name, type|
67
+ remove_column(name, type)
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ module CommandRecorder
74
+
75
+ def add_oauth
76
+ record(:add_oauthable)
77
+ end
78
+
79
+ private
80
+ def invert_add_oauth
81
+ [:remove_oauthable]
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,44 @@
1
+ module GoogleAPI
2
+ class Calendar < API
3
+
4
+ ENDPOINT = 'https://www.googleapis.com/calendar/v3'
5
+
6
+ # Fetch all calendars for a particular user, returning an array of calendar hashes.
7
+ def all
8
+ response = get('/users/me/calendarList')
9
+ response['items']
10
+ end
11
+
12
+ # Fetch a particular calendar based on the ID, returning a calendar hash.
13
+ def find(id)
14
+ get("/users/me/calendarList/#{id}")
15
+ end
16
+
17
+ # Delegates to #find, checking if the particular calendar exists.
18
+ def exists?(id)
19
+ find(id)['kind'].present?
20
+ end
21
+
22
+ # Create a calendar for a particular user, returning a calendar hash.
23
+ def create(calendar)
24
+ post('/calendars', body: calendar)
25
+ end
26
+
27
+ # Destroy a particular calendar based on the ID, returning true if successful.
28
+ def destroy(id)
29
+ delete("/calendars/#{id}")
30
+ end
31
+
32
+ private
33
+ def parse_calendar(response)
34
+ {
35
+ id: response['selfLink'],
36
+ title: response['title'],
37
+ updated_at: DateTime.parse(response['updated']),
38
+ details: response['details'],
39
+ eventFeed: response['eventFeedLink']
40
+ }
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,53 @@
1
+ module GoogleAPI
2
+ class Drive < API
3
+
4
+ ENDPOINT = 'https://www.googleapis.com/drive/v2'
5
+
6
+ # Fetch all files and folders for a particular user, returning an array of file/folder hashes.
7
+ def all
8
+ response = get('/files')
9
+ response['items']
10
+ end
11
+
12
+ # Fetch a particular file/folder based on the ID, returning a file/folder hash.
13
+ def find(id)
14
+ get("/files/#{id}")
15
+ end
16
+
17
+ # Delegates to #find, checking if the particular file/folder exists.
18
+ def exists?(id)
19
+ find(id)['kind'].present?
20
+ end
21
+
22
+ # Create a file/folder for a particular user, returning a file/folder hash.
23
+ # When you want to upload a file, pass a secondary parameter with the path to the file.
24
+ def create(object, file_path = nil)
25
+ response = if file_path
26
+ # Upload a file
27
+ upload('/files', object, file_path)
28
+ else
29
+ # Create a folder
30
+ object[:mimeType] = 'application/vnd.google-apps.folder'
31
+ post('/files', body: object)
32
+ end
33
+ response
34
+ end
35
+
36
+ # Destroy a particular file/folder based on the ID, returning true if successful.
37
+ def destroy(id)
38
+ delete("/files/#{id}")
39
+ end
40
+
41
+ private
42
+ def parse_object(response)
43
+ {
44
+ id: response['selfLink'],
45
+ title: response['title'],
46
+ updated_at: DateTime.parse(response['updated']),
47
+ details: response['details'],
48
+ eventFeed: response['eventFeedLink']
49
+ }
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,110 @@
1
+ module GoogleAPI
2
+ class API
3
+
4
+ FORMAT = :json
5
+
6
+ attr_reader :access_token
7
+
8
+ def initialize(access_token)
9
+ @access_token = access_token
10
+ end
11
+
12
+ # The really important part of this parent class. The headers are injected here,
13
+ # and the full URL is built from the ENDPOINT constant and the value passed thru
14
+ # the URL parameter.
15
+ #
16
+ # The response is then returned, and appropriately parsed.
17
+ def request(method, url = nil, args = {})
18
+ args[:headers] = headers.merge(args[:headers] || {})
19
+ args[:body] = args[:body].send("to_#{format}") if args[:body].is_a?(Hash)
20
+
21
+ url.prepend(self.class::ENDPOINT) unless URI(url).scheme
22
+
23
+ # Adopt Google's API erorr handling recommendation here:
24
+ # https://developers.google.com/drive/manage-uploads#exp-backoff
25
+ #
26
+ # In essence, we try 5 times to perform the request. With each subsequent request,
27
+ # we wait 2^n seconds plus a random number of milliseconds (no greater than 1 second)
28
+ # until either we receive a successful response, or we run out of attempts.
29
+ # If the Retry-After header is in the error response, we use whichever happens to be
30
+ # greater, our calculated wait time, or the value in the Retry-After header.
31
+ attempt = 0
32
+ while attempt < 5
33
+ response = access_token.send(method, url, args)
34
+ break unless response.status >= 500
35
+ seconds_to_wait = [((2 ** attempt) + rand), response.headers['Retry-After'].to_i].max
36
+ attempt += 1
37
+ puts "#{attempt.ordinalize} request attempt failed. Trying again in #{seconds_to_wait} seconds..."
38
+ sleep seconds_to_wait
39
+ end
40
+
41
+ if response.body.present?
42
+ case format
43
+ when :json
44
+ JSON.parse(response.body)
45
+ when :xml
46
+ Nokogiri::XML(response.body)
47
+ end
48
+ else
49
+ response
50
+ end
51
+ end
52
+
53
+ # Shortcut methods to easily execute a HTTP request with any of the below HTTP verbs.
54
+ [:get, :post, :put, :patch, :delete].each do |method|
55
+ define_method method do |url = nil, args = {}|
56
+ request(method, url, args)
57
+ end
58
+ end
59
+
60
+ # Build a resumable upload request that then delegates to #post and #put with the correct
61
+ # headers for each request.
62
+ #
63
+ # The initial POST request initiates the upload process, passing the metadata for the file.
64
+ # The response from the API includes a Location header telling us where to actually send the
65
+ # file we want uploaded. The subsequent PUT request sends the file itself to the API.
66
+ def upload(url, object, file_path)
67
+ object[:mimeType] = MIME::Types.type_for(file_path).first.to_s
68
+ file = File.read(file_path)
69
+
70
+ response = post(build_upload_url(url), body: object, headers: {'X-Upload-Content-Type' => object[:mimeType]})
71
+ put(response.headers['Location'], body: file, headers: {'Content-Type' => object[:mimeType], 'Content-Length' => file.bytesize.to_s})
72
+ end
73
+
74
+ private
75
+ # Each class that inherits from this API class can have GDATA_VERSION set as a constant.
76
+ # This is then passed on to each request if present to dictate which version of Google's
77
+ # API we intend to use.
78
+ def version
79
+ self.class::GDATA_VERSION rescue nil
80
+ end
81
+
82
+ # By default we use JSON as the format we pass to Google and get back from Google. To override
83
+ # this default setting, each class that inherits from this API class can have FORMAT set as
84
+ # a constant. The only other possible value can be XML.
85
+ def format
86
+ raise ArgumentError, "#{self.class} has FORMAT set to #{self.class::FORMAT}, which is not :json or :xml" unless [:json, :xml].include?(self.class::FORMAT)
87
+ self.class::FORMAT
88
+ end
89
+
90
+ # Build the header hash for the request. If #version is set, we pass that as GData-Version,
91
+ # and if #format is set, we pass that as Content-Type.
92
+ def headers
93
+ headers = {}
94
+ headers['GData-Version'] = version if version
95
+ headers['Content-Type'] = case format
96
+ when :json
97
+ 'application/json'
98
+ when :xml
99
+ 'application/atom+xml'
100
+ end
101
+ return headers
102
+ end
103
+
104
+ def build_upload_url(url)
105
+ path = URI(self.class::ENDPOINT).path
106
+ "https://www.googleapis.com/upload#{path}#{url}?uploadType=resumable"
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,63 @@
1
+ module GoogleAPI
2
+ class Client
3
+
4
+ attr_reader :object
5
+
6
+ # This is where the magic happens. All methods are based off of a new Client object.
7
+ # Before we go anywhere else, however, we must make sure that the object passed to the
8
+ # #new method is oauthable. If not, raise an error telling the user.
9
+ def initialize(object)
10
+ @object = object
11
+
12
+ raise NoMethodError, "GoogleAPI::Client must be passed an object that is to oauthable. #{object.class.name} is not oauthable." unless object.class.respond_to?(:oauthable)
13
+ end
14
+
15
+ # Build an AccessToken object from OAuth2. Check if the access token is expired, and if so,
16
+ # refresh it and save the new access token returned from Google.
17
+ def access_token
18
+ @access_token ||= ::OAuth2::AccessToken.new(client, oauth_hash[:access_token], oauth_hash.except(:access_token))
19
+ if @access_token.expired?
20
+ puts "Access Token expired, refreshing..."
21
+ @access_token = @access_token.refresh!
22
+ object.update_access_token!(@access_token.token)
23
+ end
24
+
25
+ @access_token
26
+ end
27
+
28
+ # Build the oauth_hash used to build the AccessToken object above. If any of the values
29
+ # are nil?, we raise an error and tell the user.
30
+ def oauth_hash
31
+ unless @oauth_hash
32
+ hash = object.oauth_hash.dup
33
+ hash[:expires_at] = hash[:expires_at].to_i if hash[:expires_at].present?
34
+ raise ArgumentError, "#{object.class.name} does not appeared to be OAuth2 authenticated. GoogleAPI requires :oauth_access_token, :oauth_request_token, and :oauth_access_token_expires_at to be present." unless hash.values.all?
35
+ end
36
+
37
+ @oauth_hash ||= hash
38
+ end
39
+
40
+ # Build the OAuth2::Client object to be used when building an AccessToken.
41
+ def client
42
+ puts "Creating the OAuth2::Client object..." unless @client
43
+ @client ||= ::OAuth2::Client.new(GoogleAPI.client_id, GoogleAPI.client_secret,
44
+ site: 'https://accounts.google.com',
45
+ token_url: '/o/oauth2/token',
46
+ raise_errors: false
47
+ )
48
+ end
49
+
50
+ # Each API that Google offers us is instantiated within its own method below.
51
+ # If you want to find all calendars for a user, you would do this:
52
+ #
53
+ # GoogleAPI::Client.new(User.first).calendar.all
54
+ #
55
+ # See GoogleAPI::Calendar for more information about the Calendar API.
56
+ %w(calendar drive).each do |api|
57
+ define_method api do
58
+ "GoogleAPI::#{api.capitalize}".constantize.new(access_token)
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,14 @@
1
+ # Adapted from Paperclip's implementation of available migration methods
2
+ # https://github.com/thoughtbot/paperclip/blob/v3.1.4/lib/paperclip/railtie.rb
3
+
4
+ require 'google-api/active_record_inclusions'
5
+
6
+ module GoogleAPI
7
+ class Railtie < Rails::Railtie
8
+
9
+ initializer "google_api.configure_rails_initialization" do |app|
10
+ ActiveRecord::Base.send(:include, ActiveRecordInclusions)
11
+ end
12
+
13
+ end
14
+ end
data/lib/google-api.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'google-api/api'
2
+ require 'google-api/api/calendar'
3
+ require 'google-api/api/drive'
4
+ require 'google-api/client'
5
+ require 'google-api/railtie'
6
+
7
+ module GoogleAPI
8
+
9
+ # This enables us to easily pass the client_id and client_secret from the Rails
10
+ # app with a GoogleAPI.configure block. See the README for more information.
11
+ # Loosely adapted from Twitter's configuration capabilities.
12
+ # https://github.com/sferik/twitter/blob/v3.6.0/lib/twitter/configurable.rb
13
+ class << self
14
+
15
+ attr_accessor :client_id, :client_secret
16
+
17
+ def configure
18
+ yield self
19
+ self
20
+ end
21
+
22
+ end
23
+
24
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: google-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.alpha
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Alex Robbin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: oauth2
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.8.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.8.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rails
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '3.2'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '3.2'
46
+ - !ruby/object:Gem::Dependency
47
+ name: bundler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: simplecov
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: A simple but powerful ruby API wrapper for Google's services.
111
+ email:
112
+ - alex@robbinsweb.biz
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - .gitignore
118
+ - .rspec
119
+ - CHANGELOG.md
120
+ - Gemfile
121
+ - LICENSE
122
+ - README.md
123
+ - Rakefile
124
+ - google-api.gemspec
125
+ - lib/google-api.rb
126
+ - lib/google-api/active_record_inclusions.rb
127
+ - lib/google-api/api.rb
128
+ - lib/google-api/api/calendar.rb
129
+ - lib/google-api/api/drive.rb
130
+ - lib/google-api/client.rb
131
+ - lib/google-api/railtie.rb
132
+ homepage: ''
133
+ licenses: []
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ! '>='
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ segments:
145
+ - 0
146
+ hash: 3955981153678874912
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ none: false
149
+ requirements:
150
+ - - ! '>'
151
+ - !ruby/object:Gem::Version
152
+ version: 1.3.1
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 1.8.24
156
+ signing_key:
157
+ specification_version: 3
158
+ summary: A simple but powerful ruby API wrapper for Google's services.
159
+ test_files: []