google-api 0.0.1.alpha → 0.0.1.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +11 -1
- data/README.md +16 -12
- data/google-api.gemspec +4 -4
- data/lib/google-api/active_record_inclusions.rb +4 -0
- data/lib/google-api/api.rb +110 -84
- data/lib/google-api/client.rb +23 -23
- data/lib/google-api/oauth2.rb +16 -0
- data/lib/google-api/railtie.rb +1 -1
- data/lib/google-api.rb +43 -6
- data/spec/fixtures/discovery_drive.json +22 -0
- data/spec/fixtures/discovery_rest_drive.json +2348 -0
- data/spec/fixtures/drive_error.json +13 -0
- data/spec/fixtures/drive_files.json +51 -0
- data/spec/fixtures/refresh_access_token.json +5 -0
- data/spec/google-api/api_spec.rb +201 -0
- data/spec/google-api/client_spec.rb +115 -0
- data/spec/google-api_spec.rb +102 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/faraday_stubs.rb +17 -0
- data/spec/support/fixture.rb +9 -0
- data/spec/support/oauth2_response.rb +17 -0
- data/spec/support/oauthable_object.rb +27 -0
- metadata +42 -17
- data/lib/google-api/api/calendar.rb +0 -44
- data/lib/google-api/api/drive.rb +0 -53
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
-
## v0.0.1.
|
1
|
+
## v0.0.1.beta (2012-09-05)
|
2
|
+
|
3
|
+
* Full test suite for all classes and modules.
|
4
|
+
* Rethink and rebuild the API integration. Now discover the API and build methods dynamically! Works with the following Google APIs:
|
5
|
+
* Calendar
|
6
|
+
* Drive
|
7
|
+
* Add logger configuration.
|
8
|
+
* Add the #google method to an oauthable object.
|
9
|
+
* Add the #patch method to OAuth2.
|
10
|
+
|
11
|
+
## v0.0.1.alpha (2012-08-21)
|
2
12
|
|
3
13
|
* First release. (be careful!)
|
data/README.md
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
-
**This gem is
|
1
|
+
**This gem is in its infancy! Proceed at your own risk...**
|
2
2
|
|
3
|
-
Google API
|
4
|
-
===================
|
3
|
+
# Google API
|
5
4
|
|
6
5
|
A simple but powerful ruby API wrapper for Google's services.
|
7
6
|
|
8
|
-
Installation
|
9
|
-
-------
|
7
|
+
## Installation
|
10
8
|
|
11
9
|
Add this line to your application's Gemfile:
|
12
10
|
|
@@ -21,17 +19,15 @@ Or install it yourself as:
|
|
21
19
|
$ gem install google-api
|
22
20
|
|
23
21
|
|
24
|
-
Before Using this Gem
|
25
|
-
-------
|
22
|
+
## Before Using this Gem
|
26
23
|
|
27
24
|
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
25
|
|
29
26
|
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
27
|
|
31
|
-
Usage
|
32
|
-
-------
|
28
|
+
## Usage
|
33
29
|
|
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
|
30
|
+
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 application that looks like this:
|
35
31
|
|
36
32
|
```ruby
|
37
33
|
GoogleAPI.configure do |config|
|
@@ -64,12 +60,20 @@ Once you have set that up, making a request to the Google API is as simple as:
|
|
64
60
|
|
65
61
|
```ruby
|
66
62
|
user = User.find(1)
|
67
|
-
|
68
|
-
client.drive.all
|
63
|
+
user.google.drive.files.list
|
69
64
|
```
|
70
65
|
|
71
66
|
This will fetch all files and folders in the user's Google Drive and return them in an array of hashes.
|
72
67
|
|
68
|
+
## What Google APIs can this gem be used for?
|
69
|
+
|
70
|
+
* Calendar
|
71
|
+
* Drive
|
72
|
+
|
73
|
+
## I need to use an API that is not yet included
|
74
|
+
|
75
|
+
Open an issue, and we will do our best to integrate and fully test the API you need. Or, you can submit a pull request with the necessary updates!
|
76
|
+
|
73
77
|
## Contributing
|
74
78
|
|
75
79
|
1. Fork it
|
data/google-api.gemspec
CHANGED
@@ -4,17 +4,17 @@ Gem::Specification.new do |gem|
|
|
4
4
|
gem.email = ["alex@robbinsweb.biz"]
|
5
5
|
gem.summary = %q{A simple but powerful ruby API wrapper for Google's services.}
|
6
6
|
gem.description = gem.summary
|
7
|
-
gem.homepage = ""
|
7
|
+
gem.homepage = "https://github.com/agrobbin/google-api"
|
8
8
|
|
9
9
|
gem.files = `git ls-files`.split($\)
|
10
10
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
11
11
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
12
12
|
gem.name = "google-api"
|
13
13
|
gem.require_paths = ["lib"]
|
14
|
-
gem.version = "0.0.1.
|
14
|
+
gem.version = "0.0.1.beta"
|
15
15
|
|
16
|
-
gem.add_runtime_dependency '
|
17
|
-
gem.add_runtime_dependency '
|
16
|
+
gem.add_runtime_dependency 'mime-types', '~> 1.0'
|
17
|
+
gem.add_runtime_dependency 'oauth2', '~> 0.8.0'
|
18
18
|
gem.add_development_dependency 'bundler'
|
19
19
|
gem.add_development_dependency 'rake'
|
20
20
|
gem.add_development_dependency 'rspec'
|
data/lib/google-api/api.rb
CHANGED
@@ -1,109 +1,135 @@
|
|
1
1
|
module GoogleAPI
|
2
2
|
class API
|
3
3
|
|
4
|
-
|
4
|
+
attr_reader :access_token, :api, :map
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
def initialize(access_token)
|
6
|
+
def initialize(access_token, api, map)
|
9
7
|
@access_token = access_token
|
8
|
+
@api = api
|
9
|
+
@map = map
|
10
10
|
end
|
11
11
|
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# the
|
12
|
+
# Taking over #method_missing here allows us to chain multiple methods onto a API
|
13
|
+
# instance. If the current place we are in the API includes this method, then let's call it!
|
14
|
+
# If not, and there are multiple methods below this section of the API, build a new API
|
15
|
+
# and do it all over again.
|
16
|
+
#
|
17
|
+
# As an example:
|
18
|
+
#
|
19
|
+
# User.find(1).google.drive.files.list => we will be calling this method twice, once to find all
|
20
|
+
# methods within the files section of the API, and once to send a request to the #list API method,
|
21
|
+
# delegating to the #request method above.
|
15
22
|
#
|
16
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
# User.find(1).google.drive.files.permissions.list => we will end up calling this method three times,
|
24
|
+
# until we get to the end of the chain.
|
25
|
+
# (Note: This method chain doesn't map to an actual Google Drive API method.)
|
26
|
+
#
|
27
|
+
# If the API method includes a mediaUpload key, we know that this method allows uploads, like to upload
|
28
|
+
# a new Google Drive file. If so, we call the #upload method instead of #request.
|
29
|
+
def method_missing(method, *args)
|
30
|
+
api_method = map[method.to_s]
|
31
|
+
args = args.last.is_a?(Hash) ? args.last : {} # basically #extract_options!
|
32
|
+
methods_or_resources = api_method['methods'] || api_method['resources']
|
33
|
+
if methods_or_resources
|
34
|
+
API.new(access_token, api, methods_or_resources)
|
35
|
+
else
|
36
|
+
url, options = build_url(api_method, args)
|
20
37
|
|
21
|
-
|
38
|
+
raise ArgumentError, ":body parameter was not passed" if !options[:body] && %w(POST PUT PATCH).include?(api_method['httpMethod'])
|
22
39
|
|
23
|
-
|
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
|
40
|
+
send(api_method['mediaUpload'] && args[:media] ? :upload : :request, api_method['httpMethod'].downcase, url, options)
|
39
41
|
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
# A really important part of this class. The headers are injected here,
|
46
|
+
# and the body is transformed into a JSON'd string when necessary.
|
47
|
+
# We do exponential back-off for error responses, and return a parsed
|
48
|
+
# response body if present, the full Response object if not.
|
49
|
+
def request(method, url = nil, options = {})
|
50
|
+
options[:headers] = {'Content-Type' => 'application/json'}.merge(options[:headers] || {})
|
51
|
+
options[:body] = options[:body].to_json if options[:body].is_a?(Hash)
|
40
52
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
53
|
+
# Adopt Google's API erorr handling recommendation here:
|
54
|
+
# https://developers.google.com/drive/handle-errors#implementing_exponential_backoff
|
55
|
+
#
|
56
|
+
# In essence, we try 5 times to perform the request. With each subsequent request,
|
57
|
+
# we wait 2^n seconds plus a random number of milliseconds (no greater than 1 second)
|
58
|
+
# until either we receive a successful response, or we run out of attempts.
|
59
|
+
# If the Retry-After header is in the error response, we use whichever happens to be
|
60
|
+
# greater, our calculated wait time, or the value in the Retry-After header.
|
61
|
+
#
|
62
|
+
# If development_mode is set to true, we only run the request once. This speeds up
|
63
|
+
# development for those using this gem.
|
64
|
+
attempt = 0
|
65
|
+
max_attempts = GoogleAPI.development_mode ? 1 : 5
|
66
|
+
while attempt < max_attempts
|
67
|
+
response = access_token.send(method.to_sym, url, options)
|
68
|
+
seconds_to_wait = [((2 ** attempt) + rand), response.headers['Retry-After'].to_i].max
|
69
|
+
attempt += 1
|
70
|
+
break if response.status < 400 || attempt == max_attempts
|
71
|
+
GoogleAPI.logger.error "Request attempt ##{attempt} to #{url} failed for. Trying again in #{seconds_to_wait} seconds..."
|
72
|
+
sleep seconds_to_wait
|
47
73
|
end
|
48
|
-
else
|
49
|
-
response
|
50
|
-
end
|
51
|
-
end
|
52
74
|
|
53
|
-
|
54
|
-
[:get, :post, :put, :patch, :delete].each do |method|
|
55
|
-
define_method method do |url = nil, args = {}|
|
56
|
-
request(method, url, args)
|
75
|
+
response.parsed || response
|
57
76
|
end
|
58
|
-
end
|
59
77
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
78
|
+
# Build a resumable upload request that then makes POST and PUT requests with the correct
|
79
|
+
# headers for each request.
|
80
|
+
#
|
81
|
+
# The initial POST request initiates the upload process, passing the metadata for the file.
|
82
|
+
# The response from the API includes a Location header telling us where to actually send the
|
83
|
+
# media we want uploaded. The subsequent PUT request sends the media itself to the API.
|
84
|
+
def upload(api_method, url, options = {})
|
85
|
+
mime_type = ::MIME::Types.type_for(options[:media]).first.to_s
|
86
|
+
file = File.read(options.delete(:media))
|
73
87
|
|
74
|
-
|
75
|
-
|
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
|
88
|
+
options[:body][:mimeType] = mime_type
|
89
|
+
options[:headers] = (options[:headers] || {}).merge({'X-Upload-Content-Type' => mime_type})
|
81
90
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
91
|
+
response = request(api_method, url, options)
|
92
|
+
|
93
|
+
options[:body] = file
|
94
|
+
options[:headers].delete('X-Upload-Content-Type')
|
95
|
+
options[:headers].merge!({'Content-Type' => mime_type, 'Content-Length' => file.bytesize.to_s})
|
96
|
+
|
97
|
+
request(:put, response.headers['Location'], options)
|
88
98
|
end
|
89
99
|
|
90
|
-
#
|
91
|
-
#
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
+
# Put together the full URL we will send a request to.
|
101
|
+
# First we join the API's base URL with the current method's path, forming the main URL.
|
102
|
+
#
|
103
|
+
# If the method is mediaUpload-enabled (like uploading a file to Google Drive), then we want
|
104
|
+
# to take the path from the resumable upload protocol.
|
105
|
+
#
|
106
|
+
# If not, then, we are going to iterate through each of the parameters for the current method.
|
107
|
+
# When the parameter's location is within the path, we first check that we have had that
|
108
|
+
# option passed, and if so, substitute it in the correct place.
|
109
|
+
# When the parameter's location is a query, we add it to our query parameters hash, provided it is present.
|
110
|
+
# Before returning the URL and remaining options, we have to build the query parameters hash
|
111
|
+
# into a string and append it to the end of the URL.
|
112
|
+
def build_url(api_method, options = {})
|
113
|
+
if api_method['mediaUpload'] && options[:media]
|
114
|
+
# we need to do [1..-1] to remove the prepended slash
|
115
|
+
url = GoogleAPI.discovered_apis[api]['rootUrl'] + api_method['mediaUpload']['protocols']['resumable']['path'][1..-1]
|
116
|
+
else
|
117
|
+
url = GoogleAPI.discovered_apis[api]['baseUrl'] + api_method['path']
|
118
|
+
query_params = []
|
119
|
+
api_method['parameters'].each_with_index do |(param, settings), index|
|
120
|
+
param = param.to_sym
|
121
|
+
case settings['location']
|
122
|
+
when 'path'
|
123
|
+
raise ArgumentError, ":#{param} was not passed" if settings['required'] && !options[param]
|
124
|
+
url.sub!("{#{param}}", options.delete(param).to_s)
|
125
|
+
when 'query'
|
126
|
+
query_params << "#{param}=#{options.delete(param)}" if options[param]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
url += "?#{query_params.join('&')}" if query_params.length > 0
|
100
130
|
end
|
101
|
-
return headers
|
102
|
-
end
|
103
131
|
|
104
|
-
|
105
|
-
path = URI(self.class::ENDPOINT).path
|
106
|
-
"https://www.googleapis.com/upload#{path}#{url}?uploadType=resumable"
|
132
|
+
[url, options]
|
107
133
|
end
|
108
134
|
|
109
135
|
end
|
data/lib/google-api/client.rb
CHANGED
@@ -9,15 +9,19 @@ module GoogleAPI
|
|
9
9
|
def initialize(object)
|
10
10
|
@object = object
|
11
11
|
|
12
|
-
raise NoMethodError, "
|
12
|
+
raise NoMethodError, "#{self.class} must be passed an object that is to oauthable. #{object.class.name} is not oauthable." unless object.class.respond_to?(:oauthable)
|
13
|
+
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 object.oauth_hash.values.all?
|
13
14
|
end
|
14
15
|
|
15
16
|
# Build an AccessToken object from OAuth2. Check if the access token is expired, and if so,
|
16
17
|
# refresh it and save the new access token returned from Google.
|
17
18
|
def access_token
|
18
|
-
@access_token
|
19
|
+
@access_token = ::OAuth2::AccessToken.new(client, object.oauth_hash[:access_token],
|
20
|
+
refresh_token: object.oauth_hash[:refresh_token],
|
21
|
+
expires_at: object.oauth_hash[:expires_at].to_i
|
22
|
+
)
|
19
23
|
if @access_token.expired?
|
20
|
-
|
24
|
+
GoogleAPI.logger.info "Access Token expired for #{object.class.name}(#{object.id}), refreshing..."
|
21
25
|
@access_token = @access_token.refresh!
|
22
26
|
object.update_access_token!(@access_token.token)
|
23
27
|
end
|
@@ -25,21 +29,8 @@ module GoogleAPI
|
|
25
29
|
@access_token
|
26
30
|
end
|
27
31
|
|
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
32
|
# Build the OAuth2::Client object to be used when building an AccessToken.
|
41
33
|
def client
|
42
|
-
puts "Creating the OAuth2::Client object..." unless @client
|
43
34
|
@client ||= ::OAuth2::Client.new(GoogleAPI.client_id, GoogleAPI.client_secret,
|
44
35
|
site: 'https://accounts.google.com',
|
45
36
|
token_url: '/o/oauth2/token',
|
@@ -47,16 +38,25 @@ module GoogleAPI
|
|
47
38
|
)
|
48
39
|
end
|
49
40
|
|
50
|
-
#
|
51
|
-
#
|
41
|
+
# We build the appropriate API here based on the method name passed to the Client.
|
42
|
+
# For example:
|
52
43
|
#
|
53
|
-
#
|
44
|
+
# User.find(1).google.drive
|
54
45
|
#
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
|
46
|
+
# We will then discover and cache the Google Drive API for future use.
|
47
|
+
# Any methods chained to the resultant API will then be passed along to the
|
48
|
+
# instantiaed class. Read the documentation for GoogleAPI::API#method_missing for
|
49
|
+
# more information.
|
50
|
+
def method_missing(api, *args)
|
51
|
+
unless GoogleAPI.discovered_apis.has_key?(api)
|
52
|
+
GoogleAPI.logger.info "Discovering the #{api} Google API..."
|
53
|
+
response = access_token.get("https://www.googleapis.com/discovery/v1/apis?preferred=true&name=#{api}").parsed['items']
|
54
|
+
super unless response # Raise a NoMethodError if Google's Discovery API does not return a good response
|
55
|
+
discovery_url = response.first['discoveryRestUrl']
|
56
|
+
GoogleAPI.discovered_apis[api] = access_token.get(discovery_url).parsed
|
59
57
|
end
|
58
|
+
|
59
|
+
API.new(access_token, api, GoogleAPI.discovered_apis[api]['resources'])
|
60
60
|
end
|
61
61
|
|
62
62
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This can go away once the below commit is released with OAuth2.
|
2
|
+
# https://github.com/intridea/oauth2/commit/8dc6feab9927c3fc03b8e0975909a96049a1cbd3
|
3
|
+
# Should be in 0.8.1 or 0.9.0
|
4
|
+
|
5
|
+
module OAuth2
|
6
|
+
class AccessToken
|
7
|
+
|
8
|
+
# Make a PATCH request with the Access Token
|
9
|
+
#
|
10
|
+
# @see AccessToken#request
|
11
|
+
def patch(path, opts={}, &block)
|
12
|
+
request(:patch, path, opts, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
data/lib/google-api/railtie.rb
CHANGED
data/lib/google-api.rb
CHANGED
@@ -1,24 +1,61 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
require 'oauth2'
|
3
|
+
require 'google-api/oauth2'
|
1
4
|
require 'google-api/api'
|
2
|
-
require 'google-api/api/calendar'
|
3
|
-
require 'google-api/api/drive'
|
4
5
|
require 'google-api/client'
|
5
|
-
|
6
|
+
if defined?(::Rails)
|
7
|
+
require 'google-api/railtie'
|
8
|
+
else
|
9
|
+
require 'logger'
|
10
|
+
end
|
6
11
|
|
7
12
|
module GoogleAPI
|
8
13
|
|
9
|
-
# This enables us to easily pass the client_id and client_secret
|
10
|
-
#
|
14
|
+
# This enables us to easily pass the client_id and client_secret with a
|
15
|
+
# GoogleAPI.configure block. See the README for more information.
|
11
16
|
# Loosely adapted from Twitter's configuration capabilities.
|
12
17
|
# https://github.com/sferik/twitter/blob/v3.6.0/lib/twitter/configurable.rb
|
13
18
|
class << self
|
14
19
|
|
15
|
-
attr_accessor :client_id, :client_secret
|
20
|
+
attr_accessor :client_id, :client_secret, :development_mode, :logger
|
16
21
|
|
22
|
+
# Configuration options:
|
23
|
+
#
|
24
|
+
# development_mode: make it easier to build your application with this API. Default is false
|
25
|
+
# logger: if this gem is included in a Rails app, we will use the Rails.logger, otherwise, we log to STDOUT
|
17
26
|
def configure
|
18
27
|
yield self
|
28
|
+
|
29
|
+
raise ArgumentError, "GoogleAPI requires both a :client_id and :client_secret configuration option to be set." unless [client_id, client_secret].all?
|
30
|
+
|
31
|
+
@development_mode ||= false
|
32
|
+
@logger ||= defined?(::Rails) ? Rails.logger : stdout_logger
|
33
|
+
|
19
34
|
self
|
20
35
|
end
|
21
36
|
|
37
|
+
# An internally used hash to cache the discovered API responses.
|
38
|
+
# Keys could be 'drive', 'calendar', 'contacts', etc.
|
39
|
+
# Values will be a parsed JSON hash.
|
40
|
+
def discovered_apis
|
41
|
+
@discovered_apis ||= {}
|
42
|
+
end
|
43
|
+
|
44
|
+
# The default logger for this API. When we aren't within a Rails app,
|
45
|
+
# we will output log messages to STDOUT.
|
46
|
+
def stdout_logger
|
47
|
+
logger = Logger.new(STDOUT)
|
48
|
+
logger.progname = "google-api"
|
49
|
+
logger
|
50
|
+
end
|
51
|
+
|
52
|
+
# Used primarily within the test suite to reset our GoogleAPI environment for each test.
|
53
|
+
def reset_environment!
|
54
|
+
@development_mode = false
|
55
|
+
@logger = nil
|
56
|
+
@discovered_apis = {}
|
57
|
+
end
|
58
|
+
|
22
59
|
end
|
23
60
|
|
24
61
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
{
|
2
|
+
"kind": "discovery#directoryList",
|
3
|
+
"discoveryVersion": "v1",
|
4
|
+
"items": [
|
5
|
+
{
|
6
|
+
"kind": "discovery#directoryItem",
|
7
|
+
"id": "drive:v2",
|
8
|
+
"name": "drive",
|
9
|
+
"version": "v2",
|
10
|
+
"title": "Drive API",
|
11
|
+
"description": "The API to interact with Drive.",
|
12
|
+
"discoveryRestUrl": "https://www.googleapis.com/discovery/v1/apis/drive/v2/rest",
|
13
|
+
"discoveryLink": "./apis/drive/v2/rest",
|
14
|
+
"icons": {
|
15
|
+
"x16": "https://ssl.gstatic.com/docs/doclist/images/drive_icon_16.png",
|
16
|
+
"x32": "https://ssl.gstatic.com/docs/doclist/images/drive_icon_32.png"
|
17
|
+
},
|
18
|
+
"documentationLink": "https://developers.google.com/drive/",
|
19
|
+
"preferred": true
|
20
|
+
}
|
21
|
+
]
|
22
|
+
}
|