google-api 0.0.1.alpha → 0.0.1.beta
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|