Skip to content

Commit 75fb55d

Browse files
author
Wynn Netherland
committed
Profile, Connection APIs
1 parent 20810bf commit 75fb55d

File tree

7 files changed

+344
-11
lines changed

7 files changed

+344
-11
lines changed

.autotest

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Autotest.add_hook(:initialize) do |at|
2+
at.add_exception(".git")
3+
end
4+
5+
Autotest.add_hook(:initialize) do |at|
6+
at.clear_mappings
7+
8+
at.add_mapping %r%/^lib/(.*)\.rb$% do |_, m|
9+
possible = File.basename(m[1])
10+
files_matching %r%^test/.*(#{possible}_test|test_#{possible})\.rb$%
11+
end
12+
13+
at.add_mapping(%r%^test/.*\.rb$%) {|filename, _| filename }
14+
end
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
= linkedin
1+
# linkedin
22

3-
Description goes here.
3+
Ruby wrapper for the [LinkedIn API](http://developer.linkedin.com)
44

5-
== Note on Patches/Pull Requests
5+
## TODO
6+
* Implement Search, Status, Invitation APIs
7+
* Swap Crack for ROXML for cleaner attribute access
8+
9+
## Note on Patches/Pull Requests
610

711
* Fork the project.
812
* Make your feature addition or bug fix.
@@ -13,6 +17,6 @@ Description goes here.
1317
bump version in a commit by itself I can ignore when I pull)
1418
* Send me a pull request. Bonus points for topic branches.
1519

16-
== Copyright
20+
## Copyright
1721

18-
Copyright (c) 2009 Wynn Netherland. See LICENSE for details.
22+
Copyright (c) 2009 [Wynn Netherland](http://wynnnetherland.com). See LICENSE for details.

Rakefile

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@ begin
55
require 'jeweler'
66
Jeweler::Tasks.new do |gem|
77
gem.name = "linkedin"
8-
gem.summary = %Q{TODO: one-line summary of your gem}
9-
gem.description = %Q{TODO: longer description of your gem}
10-
gem.email = "wynn@squeejee.com"
8+
gem.summary = %Q{Ruby wrapper for the LinkedIn API}
9+
gem.description = %Q{Ruby wrapper for the LinkedIn API}
10+
gem.email = "wynn.netherland@gmail.com"
1111
gem.homepage = "http://github.com/pengwynn/linkedin"
1212
gem.authors = ["Wynn Netherland"]
13-
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
13+
gem.files = FileList["[A-Z]*", "{examples,lib,test}/**/*"]
14+
15+
16+
gem.add_dependency('oauth', '~> 0.3.5')
17+
gem.add_dependency('hashie', '~> 0.1.3')
18+
gem.add_dependency('crack', '~> 0.1.4')
19+
20+
gem.add_development_dependency('thoughtbot-shoulda', '>= 2.10.1')
21+
gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
22+
gem.add_development_dependency('mocha', '0.9.4')
23+
gem.add_development_dependency('fakeweb', '>= 1.2.5')
1424
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
1525
end
1626
Jeweler::GemcutterTasks.new
@@ -20,8 +30,9 @@ end
2030

2131
require 'rake/testtask'
2232
Rake::TestTask.new(:test) do |test|
23-
test.libs << 'lib' << 'test'
24-
test.pattern = 'test/**/test_*.rb'
33+
test.libs << 'test'
34+
test.ruby_opts << '-rubygems'
35+
test.pattern = 'test/**/*_test.rb'
2536
test.verbose = true
2637
end
2738

lib/linked_in/client.rb

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
module LinkedIn
2+
class Client
3+
4+
attr_reader :ctoken, :csecret, :consumer_options
5+
6+
def initialize(ctoken, csecret, options={})
7+
opts = {
8+
:request_token_path => "/uas/oauth/requestToken",
9+
:access_token_path => "/uas/oauth/accessToken",
10+
:authorize_path => "/uas/oauth/authorize"
11+
}
12+
@ctoken, @csecret, @consumer_options = ctoken, csecret, opts.merge(options)
13+
end
14+
15+
def consumer
16+
@consumer ||= ::OAuth::Consumer.new(@ctoken, @csecret, {:site => 'https://api.linkedin.com'}.merge(consumer_options))
17+
end
18+
19+
def set_callback_url(url)
20+
clear_request_token
21+
request_token(:oauth_callback => url)
22+
end
23+
24+
# Note: If using oauth with a web app, be sure to provide :oauth_callback.
25+
# Options:
26+
# :oauth_callback => String, url that twitter should redirect to
27+
def request_token(options={})
28+
@request_token ||= consumer.get_request_token(options)
29+
end
30+
31+
# For web apps use params[:oauth_verifier], for desktop apps,
32+
# use the verifier is the pin that twitter gives users.
33+
def authorize_from_request(rtoken, rsecret, verifier_or_pin)
34+
request_token = ::OAuth::RequestToken.new(consumer, rtoken, rsecret)
35+
access_token = request_token.get_access_token(:oauth_verifier => verifier_or_pin)
36+
@atoken, @asecret = access_token.token, access_token.secret
37+
end
38+
39+
def access_token
40+
@access_token ||= ::OAuth::AccessToken.new(consumer, @atoken, @asecret)
41+
end
42+
43+
def authorize_from_access(atoken, asecret)
44+
@atoken, @asecret = atoken, asecret
45+
end
46+
47+
def get(path, options={})
48+
path = "/v1#{path}"
49+
puts path
50+
response = access_token.get(path, options)
51+
raise_errors(response)
52+
parse(response)
53+
end
54+
55+
56+
def profile(options={})
57+
58+
path = person_path(options)
59+
60+
unless options[:fields].nil?
61+
if options[:public]
62+
path +=":public"
63+
else
64+
path +=":(#{options[:fields].map{|f| f.to_s}.join(',')})"
65+
end
66+
end
67+
data = Hashie::Mash.new(get(path))
68+
69+
if data.errors.nil?
70+
data.person
71+
else
72+
data
73+
end
74+
75+
end
76+
77+
def connections(options={})
78+
path = "#{person_path(options)}/connections"
79+
80+
unless options[:fields].nil?
81+
if options[:public]
82+
path +=":public"
83+
else
84+
path +=":(#{options[:fields].map{|f| f.to_s}.join(',')})"
85+
end
86+
end
87+
88+
data = Hashie::Mash.new(get(path))
89+
90+
if data.errors.nil?
91+
data.connections
92+
else
93+
data
94+
end
95+
96+
end
97+
98+
private
99+
def clear_request_token
100+
@request_token = nil
101+
end
102+
103+
def raise_errors(response)
104+
case response.code.to_i
105+
when 502..503
106+
raise Unavailable, "(#{response.code}): #{response.message}"
107+
end
108+
end
109+
110+
def parse(response)
111+
Crack::XML.parse(response.body)
112+
end
113+
114+
def person_path(options)
115+
path = "/people/"
116+
if options[:id]
117+
path += "id=#{options[:id]}"
118+
elsif options[:email]
119+
path += "email=#{options[:email]}"
120+
elsif options[:url]
121+
path += "url=#{CGI.escape(options[:url])}"
122+
else
123+
path += "~"
124+
end
125+
end
126+
127+
128+
129+
end
130+
end

lib/linkedin.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
require 'forwardable'
2+
require 'rubygems'
3+
4+
gem 'oauth', '~> 0.3.5'
5+
require 'oauth'
6+
7+
gem 'hashie', '~> 0.1.3'
8+
require 'hashie'
9+
10+
gem 'crack', '~> 0.1.4'
11+
require 'crack'
12+
13+
require 'cgi'
14+
15+
directory = File.expand_path(File.dirname(__FILE__))
16+
17+
require File.join(directory, 'linked_in', 'client')

test/oauth_test.rb

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
require 'test_helper'
2+
3+
class OAuthTest < Test::Unit::TestCase
4+
should "initialize with consumer token and secret" do
5+
linkedin = LinkedIn::Client.new('token', 'secret')
6+
7+
linkedin.ctoken.should == 'token'
8+
linkedin.csecret.should == 'secret'
9+
end
10+
11+
should "set authorization path to '/uas/oauth/authorize' by default" do
12+
linkedin = LinkedIn::Client.new('token', 'secret')
13+
linkedin.consumer.options[:authorize_path].should == '/uas/oauth/authorize'
14+
end
15+
16+
should "have a consumer" do
17+
consumer = mock('oauth consumer')
18+
options = {
19+
:request_token_path => "/uas/oauth/requestToken",
20+
:access_token_path => "/uas/oauth/accessToken",
21+
:authorize_path => "/uas/oauth/authorize",
22+
:site => 'https://api.linkedin.com'
23+
}
24+
OAuth::Consumer.expects(:new).with('token', 'secret', options).returns(consumer)
25+
linkedin = LinkedIn::Client.new('token', 'secret')
26+
27+
linkedin.consumer.should == consumer
28+
end
29+
30+
should "have a request token from the consumer" do
31+
options = {
32+
:request_token_path => "/uas/oauth/requestToken",
33+
:access_token_path => "/uas/oauth/accessToken",
34+
:authorize_path => "/uas/oauth/authorize",
35+
:site => 'https://api.linkedin.com'
36+
}
37+
consumer = mock('oauth consumer')
38+
request_token = mock('request token')
39+
consumer.expects(:get_request_token).returns(request_token)
40+
OAuth::Consumer.expects(:new).with('token', 'secret', options).returns(consumer)
41+
linkedin = LinkedIn::Client.new('token', 'secret')
42+
43+
linkedin.request_token.should == request_token
44+
end
45+
46+
context "set_callback_url" do
47+
should "clear request token and set the callback url" do
48+
consumer = mock('oauth consumer')
49+
request_token = mock('request token')
50+
options = {
51+
:request_token_path => "/uas/oauth/requestToken",
52+
:access_token_path => "/uas/oauth/accessToken",
53+
:authorize_path => "/uas/oauth/authorize",
54+
:site => 'https://api.linkedin.com'
55+
}
56+
OAuth::Consumer.
57+
expects(:new).
58+
with('token', 'secret', options).
59+
returns(consumer)
60+
61+
linkedin = LinkedIn::Client.new('token', 'secret')
62+
63+
consumer.
64+
expects(:get_request_token).
65+
with({:oauth_callback => 'http://myapp.com/oauth_callback'})
66+
67+
linkedin.set_callback_url('http://myapp.com/oauth_callback')
68+
end
69+
end
70+
71+
should "be able to create access token from request token, request secret and verifier" do
72+
linkedin = LinkedIn::Client.new('token', 'secret')
73+
consumer = OAuth::Consumer.new('token', 'secret', {:site => 'https://api.linkedin.com'})
74+
linkedin.stubs(:consumer).returns(consumer)
75+
76+
access_token = mock('access token', :token => 'atoken', :secret => 'asecret')
77+
request_token = mock('request token')
78+
request_token.
79+
expects(:get_access_token).
80+
with(:oauth_verifier => 'verifier').
81+
returns(access_token)
82+
83+
OAuth::RequestToken.
84+
expects(:new).
85+
with(consumer, 'rtoken', 'rsecret').
86+
returns(request_token)
87+
88+
linkedin.authorize_from_request('rtoken', 'rsecret', 'verifier')
89+
linkedin.access_token.class.should be(OAuth::AccessToken)
90+
linkedin.access_token.token.should == 'atoken'
91+
linkedin.access_token.secret.should == 'asecret'
92+
end
93+
94+
should "be able to create access token from access token and secret" do
95+
linkedin = LinkedIn::Client.new('token', 'secret')
96+
consumer = OAuth::Consumer.new('token', 'secret', {:site => 'https://api.linkedin.com'})
97+
linkedin.stubs(:consumer).returns(consumer)
98+
99+
linkedin.authorize_from_access('atoken', 'asecret')
100+
linkedin.access_token.class.should be(OAuth::AccessToken)
101+
linkedin.access_token.token.should == 'atoken'
102+
linkedin.access_token.secret.should == 'asecret'
103+
end
104+
105+
106+
end

test/test_helper.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
require 'test/unit'
2+
require 'pathname'
3+
require 'rubygems'
4+
5+
gem 'thoughtbot-shoulda', '>= 2.10.1'
6+
gem 'jnunemaker-matchy', '0.4.0'
7+
gem 'mocha', '0.9.4'
8+
gem 'fakeweb', '>= 1.2.5'
9+
10+
require 'shoulda'
11+
require 'matchy'
12+
require 'mocha'
13+
require 'fakeweb'
14+
15+
FakeWeb.allow_net_connect = false
16+
17+
dir = (Pathname(__FILE__).dirname + '../lib').expand_path
18+
require dir + 'linkedin'
19+
20+
class Test::Unit::TestCase
21+
end
22+
23+
def fixture_file(filename)
24+
return '' if filename == ''
25+
file_path = File.expand_path(File.dirname(__FILE__) + '/fixtures/' + filename)
26+
File.read(file_path)
27+
end
28+
29+
def linkedin_url(url)
30+
url =~ /^http/ ? url : "https://api.linkedin.com:80#{url}"
31+
end
32+
33+
def stub_get(url, filename, status=nil)
34+
options = {:body => fixture_file(filename)}
35+
options.merge!({:status => status}) unless status.nil?
36+
37+
FakeWeb.register_uri(:get, linkedin_url(url), options)
38+
end
39+
40+
def stub_post(url, filename)
41+
FakeWeb.register_uri(:post, linkedin_url(url), :body => fixture_file(filename))
42+
end
43+
44+
def stub_put(url, filename)
45+
FakeWeb.register_uri(:put, linkedin_url(url), :body => fixture_file(filename))
46+
end
47+
48+
def stub_delete(url, filename)
49+
FakeWeb.register_uri(:delete, linkedin_url(url), :body => fixture_file(filename))
50+
51+
end

0 commit comments

Comments
 (0)