rgdata 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.rdoc +98 -0
- data/Rakefile +10 -0
- data/lib/rgdata/api.rb +55 -0
- data/lib/rgdata/apis/profiles.rb +18 -0
- data/lib/rgdata/authentication.rb +37 -0
- data/lib/rgdata/config.rb +34 -0
- data/lib/rgdata/model.rb +55 -0
- data/lib/rgdata/models/profile.rb +58 -0
- data/lib/rgdata/version.rb +3 -0
- data/lib/rgdata.rb +18 -0
- data/lib/support/hash.rb +27 -0
- data/mspec/api_spec.rb +13 -0
- data/mspec/apis/api_spec.rb +15 -0
- data/mspec/apis/profiles_spec.rb +27 -0
- data/mspec/authentication_spec.rb +17 -0
- data/mspec/config_spec.rb +22 -0
- data/mspec/fixtures/profiles.xml +1 -0
- data/mspec/models/profile_spec.rb +20 -0
- data/mspec/spec_helper.rb +23 -0
- data/rgdata.gemspec +25 -0
- metadata +100 -0
data/.gemtest
ADDED
File without changes
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
= RGData
|
2
|
+
|
3
|
+
RGData makes it easy to get data from Google's Data APIs.
|
4
|
+
It streamlines requests and gives you back good ole Ruby objects.
|
5
|
+
|
6
|
+
An Example:
|
7
|
+
|
8
|
+
domain = 'redningja.com'
|
9
|
+
profiles = RGData::API::Profiles.retrieve domain, oauth_token: 'access_token_thatlookslikegibberish'
|
10
|
+
=> [#<RGData::Model::Profile full_name: 'Jared Ning'>, ...]
|
11
|
+
# Now you can just do this:
|
12
|
+
profiles.first.full_name
|
13
|
+
# Want each user's email addresses?
|
14
|
+
profiles.map(&:emails)
|
15
|
+
|
16
|
+
== Installation
|
17
|
+
|
18
|
+
gem install rgdata
|
19
|
+
|
20
|
+
RGData.config do
|
21
|
+
client_id <your client_id>
|
22
|
+
client_secret <your client_secret>
|
23
|
+
end
|
24
|
+
|
25
|
+
== Authentication
|
26
|
+
|
27
|
+
To retrieve data, you need an oauth token (a.k.a. access token).
|
28
|
+
There's a lot of setup, but after that it should be easy.
|
29
|
+
|
30
|
+
* Setup
|
31
|
+
* Get your client_id and client_secret
|
32
|
+
* https://code.google.com/apis/console
|
33
|
+
* Click on Identity at the left.
|
34
|
+
* Look for OAuth2 Credentials and you'll find your client_id and client_secret
|
35
|
+
* While you're you may want to list your redirect URIs if you know what they will be. These must match your authentication requests (more on this later).
|
36
|
+
* Request authentication. (Depending on which API you are using, there may be specific instructions you may want to tell your user about. See the individual APIs classes for specifics.)
|
37
|
+
* Your user will go to the request URL generated for you by calling, for example, RGData::API::Profiles.request_url(scope, redirect_uri).
|
38
|
+
* They will be promted by Google to authenticate and grant permission.
|
39
|
+
* Successful authentication will return hash that you are responsible for collecting. It includes 3 parts:
|
40
|
+
* access_token: This is what we're after. You can use this immediately to retrieve data. You can also get one by using refresh_token which we'll get to in a sec.
|
41
|
+
* expiration: You have this many seconds to use access_token before it expires.
|
42
|
+
* refresh_token: See below.
|
43
|
+
* Once you have your access token, you can start accessing the data.
|
44
|
+
|
45
|
+
=== Refresh tokens
|
46
|
+
|
47
|
+
A refresh token won't get you into the data, but it will allow you to get more access tokens in the future. Keep this if you don't want your users to re-authenticate over and over again.
|
48
|
+
To get another access token:
|
49
|
+
|
50
|
+
RGData::API::Profiles.get_access_token(refresh_token)
|
51
|
+
|
52
|
+
For convenience, APIs are smart enough to do this work for you:
|
53
|
+
|
54
|
+
RGData::API::Profiles.retrieve domain, refresh_token: refresh_token
|
55
|
+
|
56
|
+
=== client-side and native application authentication
|
57
|
+
|
58
|
+
TODO
|
59
|
+
|
60
|
+
== Retrieving data
|
61
|
+
|
62
|
+
Easy:
|
63
|
+
|
64
|
+
# If you already have an access_token or oauth_token (same diff)
|
65
|
+
RGData::API::Profiles.retrieve domain, oauth_token: oauth_token
|
66
|
+
# If you only have a refresh token
|
67
|
+
RGData::API::Profiles.retrieve domain, refresh_token: refresh_token
|
68
|
+
# Both will return familiar, easy-to-use Ruby objects
|
69
|
+
_.first.full_name
|
70
|
+
#=> 'Jared Ning'
|
71
|
+
|
72
|
+
=== Query parameters, Retrieving a single profile, etc.
|
73
|
+
|
74
|
+
TODO
|
75
|
+
|
76
|
+
There are more ways to retrieve information (e.g. http://code.google.com/googleapps/domain/profiles/developers_guide_protocol.html#Retrieving).
|
77
|
+
Currently the gem only supports retrieving all (or at least all that Google allows in one request).
|
78
|
+
|
79
|
+
== Supported APIs
|
80
|
+
|
81
|
+
* Profiles
|
82
|
+
|
83
|
+
=== Add more
|
84
|
+
|
85
|
+
Please feel free to contribute more APIs.
|
86
|
+
Take a look at lib/rgdata/apis/profiles.rb and lib/rgdata/models/profile.rb to see how easy it is.
|
87
|
+
For the API, just find the scope (link below)
|
88
|
+
|
89
|
+
== Compatibility
|
90
|
+
|
91
|
+
Ruby 1.9. Want 1.8 support? Go fork yourself.
|
92
|
+
gem-testers: http://test.rubygems.org/gems/rgdata
|
93
|
+
|
94
|
+
== Useful links
|
95
|
+
|
96
|
+
* OAuth2 for server side: http://code.google.com/apis/accounts/docs/OAuth2.html#SS
|
97
|
+
* Scopes: http://code.google.com/apis/gdata/faq.html#AuthScopes
|
98
|
+
* Each API class should have a link to its corresponding Google API page.
|
data/Rakefile
ADDED
data/lib/rgdata/api.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module RGData::API
|
2
|
+
|
3
|
+
def self.included(target)
|
4
|
+
target.extend ClassMethods
|
5
|
+
target.instance_eval do
|
6
|
+
include HTTParty
|
7
|
+
headers 'GData-Version' => '3.0'
|
8
|
+
format :xml
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
attr_accessor :scope
|
15
|
+
attr_accessor :model
|
16
|
+
|
17
|
+
def request_url(redirect_uri)
|
18
|
+
RGData::Authentication.request_url(scope, redirect_uri)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Send request and return response.
|
22
|
+
# options can be :oauth_token, :access_token, or :refresh_token.
|
23
|
+
def do_request(domain, options)
|
24
|
+
oauth_token = extract_oauth_token_or_access_token_or_get_one_using_refresh_token(options)
|
25
|
+
query = {oauth_token: oauth_token}
|
26
|
+
query.verify_keys_present_and_values_not_nil(:oauth_token)
|
27
|
+
get "/#{domain}/full", query: query
|
28
|
+
end
|
29
|
+
|
30
|
+
# Perform request and instantiate new model objects.
|
31
|
+
def retrieve(domain, options)
|
32
|
+
full_xml_hash = do_request(domain, options)
|
33
|
+
full_xml_hash['feed']['entry'].map do |xml_hash|
|
34
|
+
model.new_from_xml_hash xml_hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Get an oauth_token from a hash.
|
41
|
+
# It can be in the form of oauth_token or access_token.
|
42
|
+
# If neither of those is found, refresh_token should be present and
|
43
|
+
# will be used to get an access_token.
|
44
|
+
def extract_oauth_token_or_access_token_or_get_one_using_refresh_token(hash)
|
45
|
+
oauth_token = hash.extract_values_at(:oauth_token, :access_token).compact.first
|
46
|
+
unless oauth_token
|
47
|
+
refresh_token = hash.extract(:refresh_token)
|
48
|
+
oauth_token = RGData::Authentication::get_access_token(refresh_token)
|
49
|
+
end
|
50
|
+
oauth_token
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# http://code.google.com/googleapps/domain/profiles/developers_guide_protocol.html
|
2
|
+
# Authentication notes:
|
3
|
+
# Google requires that the user granting permission be an administrator for their domain.
|
4
|
+
# When prompted for which account they are going to use, they should choose the one under the Google Apps section (not the default one).
|
5
|
+
|
6
|
+
module RGData::API
|
7
|
+
|
8
|
+
class Profiles
|
9
|
+
|
10
|
+
include RGData::API
|
11
|
+
|
12
|
+
base_uri 'http://www.google.com/m8/feeds/profiles/domain'
|
13
|
+
self.scope = 'http://www.google.com/m8/feeds/'
|
14
|
+
self.model = RGData::Model::Profile
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class RGData::Authentication
|
2
|
+
|
3
|
+
include HTTParty
|
4
|
+
base_uri 'https://accounts.google.com/o/oauth2'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# Construct a url for requesting authentication.
|
9
|
+
def request_url(scope, redirect_uri)
|
10
|
+
method = Net::HTTP::Get
|
11
|
+
path = default_options[:base_uri] + '/auth'
|
12
|
+
query = {
|
13
|
+
redirect_uri: redirect_uri,
|
14
|
+
scope: scope,
|
15
|
+
response_type: 'code',
|
16
|
+
client_id: RGData.config.client_id,
|
17
|
+
}
|
18
|
+
query.verify_keys_present_and_values_not_nil(:client_id, :redirect_uri, :scope, :response_type)
|
19
|
+
request = HTTParty::Request.new(method, path, query: query)
|
20
|
+
URI.unescape(request.uri.to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_access_token(refresh_token)
|
24
|
+
body = {
|
25
|
+
refresh_token: refresh_token,
|
26
|
+
grant_type: 'refresh_token',
|
27
|
+
client_id: RGData.config.client_id,
|
28
|
+
client_secret: RGData.config.client_secret,
|
29
|
+
}
|
30
|
+
body.verify_keys_present_and_values_not_nil(:refresh_token, :grant_type, :client_secret, :client_id)
|
31
|
+
response = post('/token', body: body)
|
32
|
+
response['access_token']
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class RGData::Config
|
2
|
+
|
3
|
+
configurations = [
|
4
|
+
:client_id, # https://code.google.com/apis/console, (click 'Identity' on left)
|
5
|
+
:client_secret, # https://code.google.com/apis/console, (click 'Identity' on left)
|
6
|
+
]
|
7
|
+
|
8
|
+
configurations.each do |configuration|
|
9
|
+
define_method configuration, do |*new_value|
|
10
|
+
instance_variable = :"@#{configuration}"
|
11
|
+
if new_value.any?
|
12
|
+
instance_variable_set instance_variable, new_value.first
|
13
|
+
else
|
14
|
+
instance_variable_get instance_variable
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
module RGData
|
22
|
+
|
23
|
+
class << self
|
24
|
+
|
25
|
+
def config(&block)
|
26
|
+
@config ||= RGData::Config.new
|
27
|
+
@config.instance_eval &block if block_given?
|
28
|
+
raise 'You need to set client_id and client_secret in configs' unless @config.client_id && @config.client_secret
|
29
|
+
@config
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/lib/rgdata/model.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# A class that includes RGData::Model represent an XML response from GOogle as a Ruby object.
|
2
|
+
# The class method new_from_xml_hash accepts an XML hash.
|
3
|
+
# It should be the portion of the HTTParty/Crack parsed hash that represents a single record.
|
4
|
+
# Once the module is included, the class just needs to declare fields and
|
5
|
+
# define instructions on how to drill down into the hash to get to the useful information.
|
6
|
+
|
7
|
+
module RGData::Model
|
8
|
+
|
9
|
+
def self.included(target)
|
10
|
+
target.extend ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
# Declare a field.
|
16
|
+
# It will be given attr_accessor.
|
17
|
+
# parse_block will be instructions on how to drill down into the xml_hash and get the value.
|
18
|
+
def field(name, &parse_block)
|
19
|
+
parse_blocks[name] = parse_block
|
20
|
+
attr_accessor name
|
21
|
+
end
|
22
|
+
|
23
|
+
def new_from_xml_hash(xml_hash)
|
24
|
+
attributes = parse_blocks.inject({}) do |atts, (field, parse_block)|
|
25
|
+
begin
|
26
|
+
atts[field] = parse_block.call(xml_hash)
|
27
|
+
atts
|
28
|
+
rescue Exception => ex
|
29
|
+
raise FieldError.new(field, ex.message)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
new(attributes)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def parse_blocks
|
38
|
+
@parse_blocks ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(atts = {})
|
44
|
+
atts.each do |field, value|
|
45
|
+
send "#{field}=", value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class FieldError < StandardError
|
50
|
+
def initialize(field, msg)
|
51
|
+
super "Error extracting '#{field}' field value from xml_hash: #{msg}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# This is a a single Google profile obtained from from XML, parsed into to a hash (by HTTParty/Crack), then output by awesome_print.
|
2
|
+
# {
|
3
|
+
# "id" => "http://www.google.com/m8/feeds/profiles/domain/redningja.com/full/jared",
|
4
|
+
# "updated" => "2011-03-31T15:24:04.179Z",
|
5
|
+
# "app:edited" => "2011-03-31T15:24:04.179Z",
|
6
|
+
# "category" => {
|
7
|
+
# "scheme" => "http://schemas.google.com/g/2005#kind",
|
8
|
+
# "term" => "http://schemas.google.com/contact/2008#profile"
|
9
|
+
# },
|
10
|
+
# "title" => "Jared Ning",
|
11
|
+
# "link" => [
|
12
|
+
# [0] {
|
13
|
+
# "rel" => "http://schemas.google.com/contacts/2008/rel#photo",
|
14
|
+
# "type" => "image/*",
|
15
|
+
# "href" => "https://www.google.com/m8/feeds/photos/profile/redningja.com/jared"
|
16
|
+
# },
|
17
|
+
# [1] {
|
18
|
+
# "rel" => "self",
|
19
|
+
# "type" => "application/atom+xml",
|
20
|
+
# "href" => "https://www.google.com/m8/feeds/profiles/domain/redningja.com/full/jared"
|
21
|
+
# },
|
22
|
+
# [2] {
|
23
|
+
# "rel" => "edit",
|
24
|
+
# "type" => "application/atom+xml",
|
25
|
+
# "href" => "https://www.google.com/m8/feeds/profiles/domain/redningja.com/full/jared"
|
26
|
+
# }
|
27
|
+
# ],
|
28
|
+
# "gd:name" => {
|
29
|
+
# "gd:fullName" => "Jared Ning",
|
30
|
+
# "gd:givenName" => "Jared",
|
31
|
+
# "gd:familyName" => "Ning"
|
32
|
+
# },
|
33
|
+
# "gd:email" => [
|
34
|
+
# [0] {
|
35
|
+
# "rel" => "http://schemas.google.com/g/2005#other",
|
36
|
+
# "address" => "jared@redningja.com",
|
37
|
+
# "primary" => "true"
|
38
|
+
# },
|
39
|
+
# [1] {
|
40
|
+
# "rel" => "http://schemas.google.com/g/2005#other",
|
41
|
+
# "address" => "*@redningja.com"
|
42
|
+
# }
|
43
|
+
# ],
|
44
|
+
# "gd:etag" => "\"D0NbQR5cQit7I2A4\""
|
45
|
+
# }
|
46
|
+
#
|
47
|
+
# It is possible for a profile to have multiple email addresses.
|
48
|
+
|
49
|
+
class RGData::Model::Profile
|
50
|
+
|
51
|
+
include RGData::Model
|
52
|
+
|
53
|
+
field(:full_name) { |xml_hash| xml_hash['gd:name']['gd:fullName'] }
|
54
|
+
field(:given_name) { |xml_hash| xml_hash['gd:name']['gd:givenName'] }
|
55
|
+
field(:family_name) { |xml_hash| xml_hash['gd:name']['gd:familyName'] }
|
56
|
+
field(:emails) { |xml_hash| [xml_hash['gd:email']].flatten.map { |email| email['address'] } }
|
57
|
+
|
58
|
+
end
|
data/lib/rgdata.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module RGData
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'rgdata/config'
|
7
|
+
require 'rgdata/authentication'
|
8
|
+
require 'rgdata/api'
|
9
|
+
require 'rgdata/model'
|
10
|
+
|
11
|
+
lib_requires = [
|
12
|
+
'/support/**/*.rb',
|
13
|
+
'/rgdata/models/**/*.rb',
|
14
|
+
'/rgdata/apis/**/*.rb',
|
15
|
+
]
|
16
|
+
lib_requires.each do |lib|
|
17
|
+
Dir[File.dirname(File.expand_path(__FILE__)) + lib].each { |file| require file }
|
18
|
+
end
|
data/lib/support/hash.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
# This gem was meant to help deal with the strictness of APIs
|
4
|
+
# and their inability to give good feedback for mundane detail errors.
|
5
|
+
# This method helps eliminate guessing when it comes to Request query/body/etc. hashes.
|
6
|
+
def verify_keys_present_and_values_not_nil(*keys)
|
7
|
+
keys.each do |key|
|
8
|
+
unless has_key?(key.to_sym) || has_key?(key.to_s)
|
9
|
+
raise "Options missing '#{key}'"
|
10
|
+
end
|
11
|
+
if extract(key).nil?
|
12
|
+
raise "Option '#{key}' cannot be nil"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# get a value from the given key whether it be stored as a symbol or string.
|
18
|
+
def extract(key)
|
19
|
+
self[key.to_s] || self[key.to_sym]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Same as values_at(), but uses extract().
|
23
|
+
def extract_values_at(*keys)
|
24
|
+
keys.map { |key| extract(key) }
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/mspec/api_spec.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.dirname(File.expand_path(__FILE__)) + '/spec_helper'
|
2
|
+
|
3
|
+
describe RGData::API do
|
4
|
+
|
5
|
+
it 'extracts oauth_token or access_token or gets one using refresh_token' do
|
6
|
+
klass = Class.new
|
7
|
+
klass.extend RGData::API::ClassMethods
|
8
|
+
klass.send(:extract_oauth_token_or_access_token_or_get_one_using_refresh_token, oauth_token: 'asdf').must_equal 'asdf'
|
9
|
+
klass.send(:extract_oauth_token_or_access_token_or_get_one_using_refresh_token, access_token: 'asdf').must_equal 'asdf'
|
10
|
+
klass.send(:extract_oauth_token_or_access_token_or_get_one_using_refresh_token, refresh_token: TEST_REFRESH_TOKEN).wont_be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.dirname(File.expand_path(__FILE__)) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe RGData::API do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@klass = Class.new
|
7
|
+
@klass.send :include, RGData::API
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'stores and returns scope' do
|
11
|
+
@klass.scope = 'asdf'
|
12
|
+
@klass.scope.must_equal 'asdf'
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.dirname(File.expand_path(__FILE__)) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe RGData::API::Profiles do
|
4
|
+
|
5
|
+
it 'has a scope' do
|
6
|
+
RGData::API::Profiles.scope.wont_be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'requests authentication url' do
|
10
|
+
redirect_uri = 'http://come.back.here.after.successful.authentication.com/'
|
11
|
+
request_url = RGData::API::Profiles.request_url(redirect_uri)
|
12
|
+
uri = URI.escape("https://accounts.google.com/o/oauth2/auth?redirect_uri=http://come.back.here.after.successful.authentication.com/&scope=http://www.google.com/m8/feeds/&response_type=code&client_id=#{RGData.config.client_id}")
|
13
|
+
request_url.must_equal uri
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'retrieves profiles from a domain' do
|
17
|
+
domain = 'redningja.com'
|
18
|
+
access_token = RGData::Authentication.get_access_token(TEST_REFRESH_TOKEN)
|
19
|
+
profiles = RGData::API::Profiles.retrieve(domain, oauth_token: access_token)
|
20
|
+
profiles.wont_be_empty
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'has a corresponding model' do
|
24
|
+
RGData::API::Profiles.model.must_equal RGData::Model::Profile
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.dirname(File.expand_path(__FILE__)) + '/spec_helper'
|
2
|
+
|
3
|
+
describe RGData::Authentication do
|
4
|
+
|
5
|
+
it 'provides a url for a user to request authentication' do
|
6
|
+
scope = RGData::API::Profiles.scope
|
7
|
+
redirect_uri = 'http://come.back.here.after.successful.authentication.com/'
|
8
|
+
request_url = RGData::Authentication.request_url(scope, redirect_uri)
|
9
|
+
uri = URI.escape("https://accounts.google.com/o/oauth2/auth?redirect_uri=http://come.back.here.after.successful.authentication.com/&scope=http://www.google.com/m8/feeds/&response_type=code&client_id=#{RGData.config.client_id}")
|
10
|
+
request_url.must_equal uri
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'retrieves another access_token given a refresh_token' do
|
14
|
+
RGData::Authentication.get_access_token(TEST_REFRESH_TOKEN).wont_be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.dirname(File.expand_path(__FILE__)) + '/spec_helper'
|
2
|
+
|
3
|
+
describe RGData::Config do
|
4
|
+
|
5
|
+
after :each do
|
6
|
+
# Restore configs.
|
7
|
+
RGData.config do
|
8
|
+
client_id ENV['GOOGLE_APPS_CLIENT_ID']
|
9
|
+
client_secret ENV['GOOGLE_APPS_CLIENT_SECRET']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'stores and returns client_id and client_secret' do
|
14
|
+
RGData.config do
|
15
|
+
client_id 1
|
16
|
+
client_secret 2
|
17
|
+
end
|
18
|
+
RGData.config.client_id.must_equal 1
|
19
|
+
RGData.config.client_secret.must_equal 2
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/' xmlns:gContact='http://schemas.google.com/contact/2008' xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:gd='http://schemas.google.com/g/2005' gd:etag='W/"D0cARX86cSt7I2A9WhZSFUU."'><id>redningja.com</id><updated>2011-03-31T15:24:04.119Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#profile'/><title>redningja.com's Profiles</title><link rel='alternate' type='text/html' href='http://www.google.com/'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='https://www.google.com/m8/feeds/profiles/domain/redningja.com/full'/><link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml' href='https://www.google.com/m8/feeds/profiles/domain/redningja.com/full/batch'/><link rel='self' type='application/atom+xml' href='https://www.google.com/m8/feeds/profiles/domain/redningja.com/full?max-results=60'/><author><name>redningja.com</name></author><generator version='1.0' uri='http://www.google.com/m8/feeds'>Contacts</generator><openSearch:totalResults>4</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>60</openSearch:itemsPerPage><entry gd:etag='"D0NbQR5cQit7I2A4"'><id>http://www.google.com/m8/feeds/profiles/domain/redningja.com/full/jared</id><updated>2011-03-31T15:24:04.179Z</updated><app:edited xmlns:app='http://www.w3.org/2007/app'>2011-03-31T15:24:04.179Z</app:edited><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#profile'/><title>Jared Ning</title><link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*' href='https://www.google.com/m8/feeds/photos/profile/redningja.com/jared'/><link rel='self' type='application/atom+xml' href='https://www.google.com/m8/feeds/profiles/domain/redningja.com/full/jared'/><link rel='edit' type='application/atom+xml' href='https://www.google.com/m8/feeds/profiles/domain/redningja.com/full/jared'/><gd:name><gd:fullName>Jared Ning</gd:fullName><gd:givenName>Jared</gd:givenName><gd:familyName>Ning</gd:familyName></gd:name><gd:email rel='http://schemas.google.com/g/2005#other' address='jared@redningja.com' primary='true'/><gd:email rel='http://schemas.google.com/g/2005#other' address='*@redningja.com'/></entry><entry gd:etag='"XBEHEU8MRit7I2A0"'><id>http://www.google.com/m8/feeds/profiles/domain/redningja.com/full/jwn</id><updated>2011-03-31T15:24:04.179Z</updated><app:edited xmlns:app='http://www.w3.org/2007/app'>2011-03-31T15:24:04.179Z</app:edited><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#profile'/><title>jared weining</title><link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*' href='https://www.google.com/m8/feeds/photos/profile/redningja.com/jwn'/><link rel='self' type='application/atom+xml' href='https://www.google.com/m8/feeds/profiles/domain/redningja.com/full/jwn'/><link rel='edit' type='application/atom+xml' href='https://www.google.com/m8/feeds/profiles/domain/redningja.com/full/jwn'/><gd:name><gd:fullName>jared weining</gd:fullName><gd:givenName>jared</gd:givenName><gd:familyName>weining</gd:familyName></gd:name><gd:email rel='http://schemas.google.com/g/2005#other' address='jwn@redningja.com' primary='true'/></entry><entry gd:etag='"ChRQGUxZQSt7I2A9"'><id>http://www.google.com/m8/feeds/profiles/domain/redningja.com/full/darcy</id><updated>2011-03-31T15:24:04.179Z</updated><app:edited xmlns:app='http://www.w3.org/2007/app'>2011-03-31T15:24:04.179Z</app:edited><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#profile'/><title>darcy clark</title><link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*' href='https://www.google.com/m8/feeds/photos/profile/redningja.com/darcy'/><link rel='self' type='application/atom+xml' href='https://www.google.com/m8/feeds/profiles/domain/redningja.com/full/darcy'/><link rel='edit' type='application/atom+xml' href='https://www.google.com/m8/feeds/profiles/domain/redningja.com/full/darcy'/><gd:name><gd:fullName>darcy clark</gd:fullName><gd:givenName>darcy</gd:givenName><gd:familyName>clark</gd:familyName></gd:name><gd:email rel='http://schemas.google.com/g/2005#other' address='darcy@redningja.com' primary='true'/></entry><entry gd:etag='"URFUQx5eFCt7I2A-"'><id>http://www.google.com/m8/feeds/profiles/domain/redningja.com/full/stephanie</id><updated>2011-03-31T15:24:04.180Z</updated><app:edited xmlns:app='http://www.w3.org/2007/app'>2011-03-31T15:24:04.180Z</app:edited><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#profile'/><title>stephanie adler</title><link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*' href='https://www.google.com/m8/feeds/photos/profile/redningja.com/stephanie'/><link rel='self' type='application/atom+xml' href='https://www.google.com/m8/feeds/profiles/domain/redningja.com/full/stephanie'/><link rel='edit' type='application/atom+xml' href='https://www.google.com/m8/feeds/profiles/domain/redningja.com/full/stephanie'/><gd:name><gd:fullName>stephanie adler</gd:fullName><gd:givenName>stephanie</gd:givenName><gd:familyName>adler</gd:familyName></gd:name><gd:email rel='http://schemas.google.com/g/2005#other' address='stephanie@redningja.com' primary='true'/></entry></feed>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(File.expand_path(__FILE__)) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe RGData::Model::Profile do
|
4
|
+
|
5
|
+
it 'can be created from an xml hash' do
|
6
|
+
data = [
|
7
|
+
['Jared Ning', 'Jared', 'Ning', ['jared@redningja.com', '*@redningja.com']],
|
8
|
+
['jared weining', 'jared', 'weining', ['jwn@redningja.com']],
|
9
|
+
['darcy clark', 'darcy', 'clark', ['darcy@redningja.com']],
|
10
|
+
['stephanie adler', 'stephanie', 'adler', ['stephanie@redningja.com']],
|
11
|
+
]
|
12
|
+
xml_hashes = Crack::XML.parse(fixture_file('profiles.xml'))['feed']['entry']
|
13
|
+
xml_hashes.map do |xml_hash|
|
14
|
+
profile = RGData::Model::Profile.new_from_xml_hash(xml_hash)
|
15
|
+
profile_data = [:full_name, :given_name, :family_name, :emails].map { |field| profile.send field }
|
16
|
+
profile_data
|
17
|
+
end.must_equal data
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'minitest/pride'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'pathname'
|
4
|
+
ROOT_DIR = Pathname(File.dirname(File.expand_path(__FILE__))) + '..'
|
5
|
+
$LOAD_PATH << (ROOT_DIR + 'lib').to_s
|
6
|
+
require 'rgdata'
|
7
|
+
require 'crack'
|
8
|
+
|
9
|
+
RGData.config do
|
10
|
+
client_id ENV['GOOGLE_APPS_CLIENT_ID']
|
11
|
+
client_secret ENV['GOOGLE_APPS_CLIENT_SECRET']
|
12
|
+
end
|
13
|
+
TEST_REFRESH_TOKEN = ENV['GOOGLE_APPS_REFRESH_TOKEN']
|
14
|
+
|
15
|
+
module MiniTest::Spec::Helpers
|
16
|
+
|
17
|
+
def fixture_file(file_name)
|
18
|
+
File.open(ROOT_DIR + 'mspec/fixtures/' + file_name)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
MiniTest::Unit::TestCase.send :include, MiniTest::Spec::Helpers
|
data/rgdata.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'rgdata/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'rgdata'
|
7
|
+
s.version = Rgdata::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Jared Ning']
|
10
|
+
s.email = ['jared@redningja.com']
|
11
|
+
s.homepage = 'https://github.com/ordinaryzelig/rgdata'
|
12
|
+
s.summary = %q{Google Data API for Ruby}
|
13
|
+
s.description = %q{Google Data API for Ruby}
|
14
|
+
|
15
|
+
s.rubyforge_project = 'rgdata'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split()
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split('\n')
|
19
|
+
s.executables = `git ls-files -- bin/*`.split('\n').map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
|
22
|
+
s.add_dependency 'httparty', '~> 0.7.4'
|
23
|
+
|
24
|
+
s.add_development_dependency 'minitest', '2.0.2'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rgdata
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jared Ning
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-04-01 00:00:00 -05:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: httparty
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.7.4
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - "="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 2.0.2
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
description: Google Data API for Ruby
|
39
|
+
email:
|
40
|
+
- jared@redningja.com
|
41
|
+
executables: []
|
42
|
+
|
43
|
+
extensions: []
|
44
|
+
|
45
|
+
extra_rdoc_files: []
|
46
|
+
|
47
|
+
files:
|
48
|
+
- .gemtest
|
49
|
+
- .gitignore
|
50
|
+
- Gemfile
|
51
|
+
- README.rdoc
|
52
|
+
- Rakefile
|
53
|
+
- lib/rgdata.rb
|
54
|
+
- lib/rgdata/api.rb
|
55
|
+
- lib/rgdata/apis/profiles.rb
|
56
|
+
- lib/rgdata/authentication.rb
|
57
|
+
- lib/rgdata/config.rb
|
58
|
+
- lib/rgdata/model.rb
|
59
|
+
- lib/rgdata/models/profile.rb
|
60
|
+
- lib/rgdata/version.rb
|
61
|
+
- lib/support/hash.rb
|
62
|
+
- mspec/api_spec.rb
|
63
|
+
- mspec/apis/api_spec.rb
|
64
|
+
- mspec/apis/profiles_spec.rb
|
65
|
+
- mspec/authentication_spec.rb
|
66
|
+
- mspec/config_spec.rb
|
67
|
+
- mspec/fixtures/profiles.xml
|
68
|
+
- mspec/models/profile_spec.rb
|
69
|
+
- mspec/spec_helper.rb
|
70
|
+
- rgdata.gemspec
|
71
|
+
has_rdoc: true
|
72
|
+
homepage: https://github.com/ordinaryzelig/rgdata
|
73
|
+
licenses: []
|
74
|
+
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: "0"
|
92
|
+
requirements: []
|
93
|
+
|
94
|
+
rubyforge_project: rgdata
|
95
|
+
rubygems_version: 1.6.1
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: Google Data API for Ruby
|
99
|
+
test_files: []
|
100
|
+
|