linkedin 0.4.6 → 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -1
  3. data/.yardopts +7 -0
  4. data/{changelog.markdown → CHANGELOG.md} +5 -1
  5. data/EXAMPLES.md +199 -0
  6. data/Gemfile +4 -0
  7. data/README.md +43 -0
  8. data/Rakefile +2 -7
  9. data/lib/linked_in/api.rb +34 -2
  10. data/lib/linked_in/api/communications.rb +44 -0
  11. data/lib/linked_in/api/companies.rb +128 -0
  12. data/lib/linked_in/api/groups.rb +115 -0
  13. data/lib/linked_in/api/jobs.rb +64 -0
  14. data/lib/linked_in/api/people.rb +72 -0
  15. data/lib/linked_in/api/query_helpers.rb +86 -0
  16. data/lib/linked_in/api/share_and_social_stream.rb +133 -0
  17. data/lib/linked_in/client.rb +7 -2
  18. data/lib/linked_in/errors.rb +12 -2
  19. data/lib/linked_in/mash.rb +31 -4
  20. data/lib/linked_in/search.rb +16 -1
  21. data/lib/linked_in/version.rb +1 -1
  22. data/lib/linkedin.rb +4 -1
  23. data/linkedin.gemspec +5 -3
  24. data/spec/cases/api_spec.rb +15 -5
  25. data/spec/cases/mash_spec.rb +30 -2
  26. data/spec/cases/oauth_spec.rb +5 -6
  27. data/spec/cases/search_spec.rb +53 -23
  28. data/spec/fixtures/cassette_library/LinkedIn_Api/Company_API.yml +3 -3
  29. data/spec/fixtures/cassette_library/LinkedIn_Api/Company_API/should_load_correct_company_data.yml +81 -0
  30. data/spec/fixtures/cassette_library/LinkedIn_Client/{_authorize_from_request.yml → _authorize_from_request/should_return_a_valid_access_token.yml} +0 -0
  31. data/spec/fixtures/cassette_library/LinkedIn_Client/_request_token/{with_a_callback_url.yml → with_a_callback_url/should_return_a_valid_access_token.yml} +0 -0
  32. data/spec/fixtures/cassette_library/LinkedIn_Client/_request_token/{with_default_options.yml → with_default_options/should_return_a_valid_request_token.yml} +0 -0
  33. data/spec/fixtures/cassette_library/LinkedIn_Search/_search/{by_company_name_option.yml → by_company_name_option/should_perform_a_search.yml} +8 -9
  34. data/spec/fixtures/cassette_library/LinkedIn_Search/_search/by_email_address/should_perform_a_people_search.yml +57 -0
  35. data/spec/fixtures/cassette_library/LinkedIn_Search/_search/{by_first_name_and_last_name_options.yml → by_first_name_and_last_name_options/should_perform_a_search.yml} +15 -15
  36. data/spec/fixtures/cassette_library/LinkedIn_Search/_search/by_first_name_and_last_name_options_with_fields/should_perform_a_search.yml +114 -0
  37. data/spec/fixtures/cassette_library/LinkedIn_Search/_search/{by_keywords_string_parameter.yml → by_keywords_string_parameter/should_perform_a_search.yml} +8 -9
  38. data/spec/fixtures/cassette_library/LinkedIn_Search/_search/by_multiple_email_address/should_perform_a_multi-email_search.yml +59 -0
  39. data/spec/fixtures/cassette_library/LinkedIn_Search/_search/{by_single_keywords_option.yml → by_single_keywords_option/should_perform_a_search.yml} +8 -9
  40. data/spec/fixtures/cassette_library/LinkedIn_Search/_search/{by_single_keywords_option_with_pagination.yml → by_single_keywords_option_with_pagination/should_perform_a_search.yml} +1 -3
  41. data/spec/fixtures/cassette_library/LinkedIn_Search/_search/email_search_returns_unauthorized/should_raise_an_unauthorized_error.yml +59 -0
  42. data/spec/fixtures/cassette_library/LinkedIn_Search/_search_company/by_keywords_options_with_fields/should_perform_a_search.yml +43 -0
  43. data/spec/fixtures/cassette_library/LinkedIn_Search/_search_company/{by_keywords_string_parameter.yml → by_keywords_string_parameter/should_perform_a_company_search.yml} +19 -19
  44. data/spec/fixtures/cassette_library/LinkedIn_Search/_search_company/{by_single_keywords_option.yml → by_single_keywords_option/should_perform_a_company_search.yml} +19 -19
  45. data/spec/fixtures/cassette_library/LinkedIn_Search/_search_company/{by_single_keywords_option_with_facets_to_return.yml → by_single_keywords_option_with_facets_to_return/should_return_a_facet.yml} +14 -14
  46. data/spec/fixtures/cassette_library/LinkedIn_Search/_search_company/{by_single_keywords_option_with_pagination.yml → by_single_keywords_option_with_pagination/should_perform_a_search.yml} +15 -15
  47. data/spec/helper.rb +9 -5
  48. metadata +76 -61
  49. data/.document +0 -5
  50. data/README.markdown +0 -84
  51. data/examples/authenticate.rb +0 -26
  52. data/examples/communication.rb +0 -7
  53. data/examples/network.rb +0 -12
  54. data/examples/profile.rb +0 -18
  55. data/examples/sinatra.rb +0 -77
  56. data/examples/status.rb +0 -6
  57. data/lib/linked_in/api/query_methods.rb +0 -176
  58. data/lib/linked_in/api/update_methods.rb +0 -85
  59. data/spec/fixtures/cassette_library/LinkedIn_Client/_request_token.yml +0 -37
  60. data/spec/fixtures/cassette_library/LinkedIn_Search/_search/by_first_name_and_last_name_options_with_fields.yml +0 -112
  61. data/spec/fixtures/cassette_library/LinkedIn_Search/_search_company/by_keywords_options_with_fields.yml +0 -232
@@ -5,8 +5,13 @@ module LinkedIn
5
5
  class Client
6
6
  include Helpers::Request
7
7
  include Helpers::Authorization
8
- include Api::QueryMethods
9
- include Api::UpdateMethods
8
+ include Api::QueryHelpers
9
+ include Api::People
10
+ include Api::Groups
11
+ include Api::Companies
12
+ include Api::Jobs
13
+ include Api::ShareAndSocialStream
14
+ include Api::Communications
10
15
  include Search
11
16
 
12
17
  attr_reader :consumer_token, :consumer_secret, :consumer_options
@@ -8,12 +8,22 @@ module LinkedIn
8
8
  end
9
9
  end
10
10
 
11
+ # Raised when a 401 response status code is received
11
12
  class UnauthorizedError < LinkedInError; end
13
+
14
+ # Raised when a 400 response status code is received
12
15
  class GeneralError < LinkedInError; end
16
+
17
+ # Raised when a 403 response status code is received
13
18
  class AccessDeniedError < LinkedInError; end
14
19
 
15
- class UnavailableError < StandardError; end
16
- class InformLinkedInError < StandardError; end
20
+ # Raised when a 404 response status code is received
17
21
  class NotFoundError < StandardError; end
22
+
23
+ # Raised when a 500 response status code is received
24
+ class InformLinkedInError < StandardError; end
25
+
26
+ # Raised when a 502 or 503 response status code is received
27
+ class UnavailableError < StandardError; end
18
28
  end
19
29
  end
@@ -2,15 +2,23 @@ require 'hashie'
2
2
  require 'multi_json'
3
3
 
4
4
  module LinkedIn
5
+
6
+ # The generalized pseudo-object that is returned for all query
7
+ # requests.
5
8
  class Mash < ::Hashie::Mash
6
9
 
7
- # a simple helper to convert a json string to a Mash
10
+ # Convert a json string to a Mash
11
+ #
12
+ # @param [String] json_string
13
+ # @return [LinkedIn::Mash]
8
14
  def self.from_json(json_string)
9
15
  result_hash = ::MultiJson.decode(json_string)
10
16
  new(result_hash)
11
17
  end
12
18
 
13
- # returns a Date if we have year, month and day, and no conflicting key
19
+ # Returns a Date if we have year, month and day, and no conflicting key
20
+ #
21
+ # @return [Date]
14
22
  def to_date
15
23
  if !self.has_key?('to_date') && contains_date_fields?
16
24
  Date.civil(self.year, self.month, self.day)
@@ -19,6 +27,20 @@ module LinkedIn
19
27
  end
20
28
  end
21
29
 
30
+ # Returns the id of the object from LinkedIn
31
+ #
32
+ # @return [String]
33
+ def id
34
+ if self['id']
35
+ self['id']
36
+ else
37
+ self['_key']
38
+ end
39
+ end
40
+
41
+ # Convert the 'timestamp' field from a string to a Time object
42
+ #
43
+ # @return [Time]
22
44
  def timestamp
23
45
  value = self['timestamp']
24
46
  if value.kind_of? Integer
@@ -29,6 +51,13 @@ module LinkedIn
29
51
  end
30
52
  end
31
53
 
54
+ # Return the results array from the query
55
+ #
56
+ # @return [Array]
57
+ def all
58
+ super || []
59
+ end
60
+
32
61
  protected
33
62
 
34
63
  def contains_date_fields?
@@ -39,8 +68,6 @@ module LinkedIn
39
68
  # keys are made a little more ruby-ish
40
69
  def convert_key(key)
41
70
  case key.to_s
42
- when '_key'
43
- 'id'
44
71
  when '_total'
45
72
  'total'
46
73
  when 'values'
@@ -1,6 +1,21 @@
1
1
  module LinkedIn
2
2
 
3
3
  module Search
4
+
5
+ # Retrieve search results of the given object type
6
+ #
7
+ # Permissions: (for people search only) r_network
8
+ #
9
+ # @note People Search API is a part of the Vetted API Access Program. You
10
+ # must apply and get approval before using this API
11
+ #
12
+ # @see http://developer.linkedin.com/documents/people-search-api People Search
13
+ # @see http://developer.linkedin.com/documents/job-search-api Job Search
14
+ # @see http://developer.linkedin.com/documents/company-search Company Search
15
+ #
16
+ # @param [Hash] options search input fields
17
+ # @param [String] type type of object to return ('people', 'job' or 'company')
18
+ # @return [LinkedIn::Mash]
4
19
  def search(options={}, type='people')
5
20
 
6
21
  path = "/#{type.to_s}-search"
@@ -53,4 +68,4 @@ module LinkedIn
53
68
  end
54
69
  end
55
70
 
56
- end
71
+ end
@@ -3,7 +3,7 @@ module LinkedIn
3
3
  module VERSION #:nodoc:
4
4
  MAJOR = 0
5
5
  MINOR = 4
6
- PATCH = 6
6
+ PATCH = 7
7
7
  PRE = nil
8
8
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
9
9
  end
@@ -7,15 +7,18 @@ module LinkedIn
7
7
 
8
8
  # config/initializers/linkedin.rb (for instance)
9
9
  #
10
+ # ```ruby
10
11
  # LinkedIn.configure do |config|
11
12
  # config.token = 'consumer_token'
12
13
  # config.secret = 'consumer_secret'
13
14
  # config.default_profile_fields = ['educations', 'positions']
14
15
  # end
15
- #
16
+ # ```
16
17
  # elsewhere
17
18
  #
19
+ # ```ruby
18
20
  # client = LinkedIn::Client.new
21
+ # ```
19
22
  def configure
20
23
  yield self
21
24
  true
@@ -2,12 +2,13 @@
2
2
  require File.expand_path('../lib/linked_in/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.add_dependency 'hashie', ['>= 1.2', '< 2.1']
5
+ gem.add_dependency 'hashie', '~> 2.0'
6
6
  gem.add_dependency 'multi_json', '~> 1.0'
7
7
  gem.add_dependency 'oauth', '~> 0.4'
8
8
  # gem.add_development_dependency 'json', '~> 1.6'
9
9
  gem.add_development_dependency 'rake', '~> 10'
10
- gem.add_development_dependency 'rdoc', '~> 4.0'
10
+ gem.add_development_dependency 'yard'
11
+ gem.add_development_dependency 'kramdown'
11
12
  gem.add_development_dependency 'rspec', '~> 2.13'
12
13
  gem.add_development_dependency 'simplecov', '~> 0.7'
13
14
  gem.add_development_dependency 'vcr', '~> 2.5'
@@ -18,8 +19,9 @@ Gem::Specification.new do |gem|
18
19
  gem.files = `git ls-files`.split("\n")
19
20
  gem.homepage = 'http://github.com/hexgnu/linkedin'
20
21
  gem.name = 'linkedin'
22
+ gem.licenses = %w[MIT]
21
23
  gem.require_paths = ['lib']
22
- gem.summary = gem.description
24
+ gem.summary = 'This gem interfaces with the Linkedin XML and JSON APis'
23
25
  gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
26
  gem.version = LinkedIn::VERSION::STRING
25
27
  end
@@ -30,6 +30,12 @@ describe LinkedIn::Api do
30
30
  client.connections.should be_an_instance_of(LinkedIn::Mash)
31
31
  end
32
32
 
33
+ it "should be able to view new connections" do
34
+ modified_since = Time.now.to_i * 1000
35
+ stub_request(:get, "https://api.linkedin.com/v1/people/~/connections?modified=new&modified-since=#{modified_since}").to_return(:body => "{}")
36
+ client.new_connections(modified_since).should be_an_instance_of(LinkedIn::Mash)
37
+ end
38
+
33
39
  it "should be able to view network_updates" do
34
40
  stub_request(:get, "https://api.linkedin.com/v1/people/~/network/updates").to_return(:body => "{}")
35
41
  client.network_updates.should be_an_instance_of(LinkedIn::Mash)
@@ -116,8 +122,7 @@ describe LinkedIn::Api do
116
122
  request_token.should == "request_token"
117
123
  end
118
124
 
119
- context "Company API" do
120
- use_vcr_cassette
125
+ context "Company API", :vcr do
121
126
 
122
127
  it "should be able to view a company profile" do
123
128
  stub_request(:get, "https://api.linkedin.com/v1/companies/id=1586").to_return(:body => "{}")
@@ -194,8 +199,7 @@ describe LinkedIn::Api do
194
199
 
195
200
  end
196
201
 
197
- context "Job API" do
198
- use_vcr_cassette
202
+ context "Job API", :vcr do
199
203
 
200
204
  it "should be able to view a job listing" do
201
205
  stub_request(:get, "https://api.linkedin.com/v1/jobs/id=1586").to_return(:body => "{}")
@@ -227,9 +231,15 @@ describe LinkedIn::Api do
227
231
  client.group_memberships.should be_an_instance_of(LinkedIn::Mash)
228
232
  end
229
233
 
234
+ it "should be able to list suggested groups for a profile" do
235
+ stub_request(:get, "https://api.linkedin.com/v1/people/~/suggestions/groups").to_return(:body => '{"id": "123"}')
236
+ response = client.group_suggestions
237
+ response.id.should == '123'
238
+ end
239
+
230
240
  it "should be able to parse nested fields" do
231
241
  stub_request(:get, "https://api.linkedin.com/v1/people/~/group-memberships:(group:(id,name,small-logo-url,short-description))").to_return(:body => "{}")
232
- client.group_memberships(fields: [{group: ['id', 'name', 'small-logo-url', 'short-description']}]).should be_an_instance_of(LinkedIn::Mash)
242
+ client.group_memberships(:fields => [{:group => ['id', 'name', 'small-logo-url', 'short-description']}]).should be_an_instance_of(LinkedIn::Mash)
233
243
  end
234
244
 
235
245
  it "should be able to join a group" do
@@ -18,6 +18,7 @@ describe LinkedIn::Mash do
18
18
  'firstName' => 'Josh',
19
19
  'LastName' => 'Kalderimis',
20
20
  '_key' => 1234,
21
+ 'id' => 1345,
21
22
  '_total' => 1234,
22
23
  'values' => {},
23
24
  'numResults' => 'total_results'
@@ -29,8 +30,23 @@ describe LinkedIn::Mash do
29
30
  mash.should have_key('last_name')
30
31
  end
31
32
 
32
- it "should convert the key _key to id" do
33
- mash.should have_key('id')
33
+ # this breaks data coming back from linkedIn
34
+ it "converts _key to id if there is an id column" do
35
+ mash._key.should == 1234
36
+ mash.id.should == 1345
37
+ end
38
+
39
+ context 'no collision' do
40
+ let(:mash) {
41
+ LinkedIn::Mash.new({
42
+ '_key' => 1234
43
+ })
44
+
45
+ }
46
+ it 'converts _key to id if there is no collision' do
47
+ mash.id.should == 1234
48
+ mash._key.should == 1234
49
+ end
34
50
  end
35
51
 
36
52
  it "should convert the key _total to total" do
@@ -82,4 +98,16 @@ describe LinkedIn::Mash do
82
98
  end
83
99
  end
84
100
 
101
+ describe "#all" do
102
+ let(:all_mash) do
103
+ LinkedIn::Mash.new({
104
+ :values => nil
105
+ })
106
+ end
107
+
108
+ it "should return an empty array if values is nil due to no results being found for a query" do
109
+ all_mash.all.should == []
110
+ end
111
+ end
112
+
85
113
  end
@@ -100,8 +100,8 @@ describe "LinkedIn::Client" do
100
100
  end
101
101
 
102
102
  describe "#request_token" do
103
- describe "with default options" do
104
- use_vcr_cassette :record => :new_episodes
103
+ vcr_options = { :record => :new_episodes}
104
+ describe "with default options", vcr: vcr_options do
105
105
 
106
106
  it "should return a valid request token" do
107
107
  request_token = client.request_token
@@ -113,8 +113,7 @@ describe "LinkedIn::Client" do
113
113
  end
114
114
  end
115
115
 
116
- describe "with a callback url" do
117
- use_vcr_cassette :record => :new_episodes
116
+ describe "with a callback url", vcr: vcr_options do
118
117
 
119
118
  it "should return a valid access token" do
120
119
  request_token = client.request_token(:oauth_callback => 'http://www.josh.com')
@@ -141,9 +140,9 @@ describe "LinkedIn::Client" do
141
140
  client.authorize_from_request('dummy-token', 'dummy-secret', 'dummy-pin')
142
141
  end
143
142
 
144
- use_vcr_cassette :record => :new_episodes, :match_requests_on => [:uri, :method]
143
+ vcr_options = { :record => :new_episodes, :match_requests_on => [ :uri, :method] }
145
144
 
146
- it "should return a valid access token" do
145
+ it "should return a valid access token", vcr: vcr_options do
147
146
  access_token.should be_a_kind_of Array
148
147
  access_token[0].should be_a_kind_of String
149
148
  access_token[1].should be_a_kind_of String
@@ -16,10 +16,11 @@ describe LinkedIn::Search do
16
16
  client
17
17
  end
18
18
 
19
+ vcr_options = { :record => :new_episodes}
20
+
19
21
  describe "#search_company" do
20
22
 
21
- describe "by keywords string parameter" do
22
- use_vcr_cassette :record => :new_episodes
23
+ describe "by keywords string parameter", vcr: vcr_options do
23
24
 
24
25
  let(:results) do
25
26
  client.search('apple', :company)
@@ -32,8 +33,7 @@ describe LinkedIn::Search do
32
33
  end
33
34
  end
34
35
 
35
- describe "by single keywords option" do
36
- use_vcr_cassette :record => :new_episodes
36
+ describe "by single keywords option", vcr: vcr_options do
37
37
 
38
38
  let(:results) do
39
39
  options = {:keywords => 'apple'}
@@ -47,8 +47,7 @@ describe LinkedIn::Search do
47
47
  end
48
48
  end
49
49
 
50
- describe "by single keywords option with facets to return" do
51
- use_vcr_cassette :record => :new_episodes
50
+ describe "by single keywords option with facets to return", vcr: vcr_options do
52
51
 
53
52
  let(:results) do
54
53
  options = {:keywords => 'apple', :facets => [:industry]}
@@ -60,8 +59,7 @@ describe LinkedIn::Search do
60
59
  end
61
60
  end
62
61
 
63
- describe "by single keywords option with pagination" do
64
- use_vcr_cassette :record => :new_episodes
62
+ describe "by single keywords option with pagination", vcr: vcr_options do
65
63
 
66
64
  let(:results) do
67
65
  options = {:keywords => 'apple', :start => 5, :count => 5}
@@ -77,8 +75,7 @@ describe LinkedIn::Search do
77
75
  end
78
76
  end
79
77
 
80
- describe "by keywords options with fields" do
81
- use_vcr_cassette :record => :new_episodes
78
+ describe "by keywords options with fields", vcr: vcr_options do
82
79
 
83
80
  let(:results) do
84
81
  fields = [{:companies => [:id, :name, :industries, :description, :specialties]}, :num_results]
@@ -96,8 +93,7 @@ describe LinkedIn::Search do
96
93
 
97
94
  describe "#search" do
98
95
 
99
- describe "by keywords string parameter" do
100
- use_vcr_cassette :record => :new_episodes
96
+ describe "by keywords string parameter", vcr: vcr_options do
101
97
 
102
98
  let(:results) do
103
99
  client.search('github')
@@ -111,8 +107,7 @@ describe LinkedIn::Search do
111
107
  end
112
108
  end
113
109
 
114
- describe "by single keywords option" do
115
- use_vcr_cassette :record => :new_episodes
110
+ describe "by single keywords option", vcr: vcr_options do
116
111
 
117
112
  let(:results) do
118
113
  client.search(:keywords => 'github')
@@ -126,8 +121,7 @@ describe LinkedIn::Search do
126
121
  end
127
122
  end
128
123
 
129
- describe "by single keywords option with pagination" do
130
- use_vcr_cassette :record => :new_episodes
124
+ describe "by single keywords option with pagination", vcr: vcr_options do
131
125
 
132
126
  let(:results) do
133
127
  client.search(:keywords => 'github', :start => 5, :count => 5)
@@ -141,8 +135,7 @@ describe LinkedIn::Search do
141
135
  end
142
136
  end
143
137
 
144
- describe "by first_name and last_name options" do
145
- use_vcr_cassette :record => :new_episodes
138
+ describe "by first_name and last_name options", vcr: vcr_options do
146
139
 
147
140
  let(:results) do
148
141
  client.search(:first_name => 'Charles', :last_name => 'Garcia')
@@ -156,8 +149,46 @@ describe LinkedIn::Search do
156
149
  end
157
150
  end
158
151
 
159
- describe "by first_name and last_name options with fields" do
160
- use_vcr_cassette :record => :new_episodes
152
+ describe "by email address", vcr: vcr_options do
153
+
154
+ let(:results) do
155
+ fields = ['id']
156
+ client.profile(:email => 'email=yy@zz.com', :fields => fields)
157
+ end
158
+
159
+ it "should perform a people search" do
160
+ results._total.should == 1
161
+ output = results["values"]
162
+ output.each do |record|
163
+ record.id.should == '96GVfLeWjU'
164
+ record._key.should == 'email=yy@zz.com'
165
+ end
166
+ end
167
+ end
168
+
169
+ describe "by multiple email address", vcr: vcr_options do
170
+
171
+ let(:results) do
172
+ fields = ['id']
173
+ client.profile(:email => 'email=yy@zz.com,email=xx@yy.com', :fields => fields)
174
+ end
175
+
176
+ it "should perform a multi-email search" do
177
+ results._total.should == 2
178
+ output = results["values"]
179
+ output.count.should == 2
180
+ end
181
+ end
182
+
183
+ describe "email search returns unauthorized", vcr: vcr_options do
184
+
185
+ it "should raise an unauthorized error" do
186
+ fields = ['id']
187
+ expect {client.profile(:email => 'email=aa@bb.com', :fields => fields)}.to raise_error(LinkedIn::Errors::UnauthorizedError)
188
+ end
189
+ end
190
+
191
+ describe "by first_name and last_name options with fields", vcr: vcr_options do
161
192
 
162
193
  let(:results) do
163
194
  fields = [{:people => [:id, :first_name, :last_name, :public_profile_url, :picture_url]}, :num_results]
@@ -175,8 +206,7 @@ describe LinkedIn::Search do
175
206
  end
176
207
  end
177
208
 
178
- describe "by company_name option" do
179
- use_vcr_cassette :record => :new_episodes
209
+ describe "by company_name option", vcr: vcr_options do
180
210
 
181
211
  let(:results) do
182
212
  client.search(:company_name => 'IBM')
@@ -201,4 +231,4 @@ describe LinkedIn::Search do
201
231
 
202
232
  end
203
233
 
204
- end
234
+ end