career_builder 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/Gemfile +10 -0
- data/LICENSE +20 -0
- data/README.md +91 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/career_builder.gemspec +95 -0
- data/lib/career_builder.rb +39 -0
- data/lib/career_builder/api/company.rb +17 -0
- data/lib/career_builder/api/interest.rb +16 -0
- data/lib/career_builder/api/job_type.rb +15 -0
- data/lib/career_builder/api/language.rb +15 -0
- data/lib/career_builder/api/location.rb +19 -0
- data/lib/career_builder/api/pay.rb +16 -0
- data/lib/career_builder/api/resume.rb +67 -0
- data/lib/career_builder/api/resume_search.rb +22 -0
- data/lib/career_builder/api/resume_search_result.rb +28 -0
- data/lib/career_builder/api/school.rb +18 -0
- data/lib/career_builder/api/shift_preference.rb +15 -0
- data/lib/career_builder/api/word_document.rb +16 -0
- data/lib/career_builder/client.rb +37 -0
- data/lib/career_builder/errors.rb +7 -0
- data/lib/career_builder/request.rb +82 -0
- data/lib/career_builder/request/authenticated.rb +23 -0
- data/lib/career_builder/requests/advanced_resume_search.rb +41 -0
- data/lib/career_builder/requests/authentication.rb +25 -0
- data/lib/career_builder/requests/get_resume.rb +24 -0
- data/lib/career_builder/requests/resume_actions_remaining_today.rb +21 -0
- data/lib/career_builder/resume.rb +44 -0
- data/lib/career_builder/resume/lazy_collection.rb +39 -0
- data/spec/career_builder/client_spec.rb +554 -0
- data/spec/career_builder/resume/lazy_collection_spec.rb +30 -0
- data/spec/career_builder/resume_spec.rb +5 -0
- data/spec/career_builder_spec.rb +5 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/webmock.rb +7 -0
- metadata +169 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
module CareerBuilder
|
2
|
+
|
3
|
+
module API
|
4
|
+
|
5
|
+
class Resume
|
6
|
+
|
7
|
+
# Sample response:
|
8
|
+
# http://ws.careerbuilder.com/resumes/resumes.asmx/V2_GetResume_SampleResponse
|
9
|
+
|
10
|
+
include HappyMapper
|
11
|
+
|
12
|
+
tag 'Packet'
|
13
|
+
|
14
|
+
# element :home_location, String, :tag => "HomeLocation"
|
15
|
+
# element :last_update, Date, :tag => "LastUpdate"
|
16
|
+
# element :title, String, :tag => "ResumeTitle"
|
17
|
+
# element :job_title, String, :tag => "JobTitle"
|
18
|
+
# element :recent_employer, String, :tag => "RecentEmployer"
|
19
|
+
# element :recent_job_title, String, :tag => "RecentJobTitle"
|
20
|
+
# element :recent_pay, String, :tag => "RecentPay"
|
21
|
+
# element :user_did, String, :tag => "UserDID"
|
22
|
+
# element :contact_email_md5, String, :tag => "ContactEmailMD5"
|
23
|
+
|
24
|
+
has_one :home_location, Location, :tag => "HomeLocation"
|
25
|
+
has_many :relocations, Location, :tag => "ExtLocation"
|
26
|
+
|
27
|
+
has_one :most_recent_pay, Pay, :tag => "MostRecentPay"
|
28
|
+
has_one :desired_pay, Pay, :tag => "DesiredPay"
|
29
|
+
|
30
|
+
has_many :interests, Interest, :tag => "ExtInterest"
|
31
|
+
has_many :companies, Company, :tag => "ExtCompany"
|
32
|
+
has_many :schools, School, :tag => "ExtSchool"
|
33
|
+
has_many :desired_job_types, JobType, :tag => "DesiredJobTypes"
|
34
|
+
has_many :languages, Language, :tag => "Languages"
|
35
|
+
has_many :desired_shift_preferences, ShiftPreference, :tag => "DesiredShiftPreferences"
|
36
|
+
|
37
|
+
element :timestamp, Time, :tag => "TimeStamp"
|
38
|
+
element :id, String, :tag => "ResumeID"
|
39
|
+
element :title, String, :tag => "ResumeTitle"
|
40
|
+
element :contact_name, String, :tag => "ContactName"
|
41
|
+
element :contact_email, String, :tag => "ContactEmail"
|
42
|
+
element :contact_phone, String, :tag => "ContactPhone"
|
43
|
+
element :max_commute_miles, Integer, :tag => "MaxCommuteMiles"
|
44
|
+
element :travel_preference, String, :tag => "TravelPreference"
|
45
|
+
element :currently_employed, String, :tag => "CurrentlyEmployed"
|
46
|
+
element :most_recent_title, String, :tag => "MostRecentTitle"
|
47
|
+
element :experience_months, Integer, :tag => "ExperienceMonths"
|
48
|
+
element :managed_others, String, :tag => "//ManagedOthers"
|
49
|
+
element :number_managed, Integer, :tag => "//NumberManaged"
|
50
|
+
element :jobs_last_three_years, Integer, :tag => "JobsLastThreeYears"
|
51
|
+
element :last_job_tenure_months, Integer, :tag => "LastJobTenureMonths"
|
52
|
+
element :security_clearance, String, :tag => "SecurityClearance"
|
53
|
+
element :felony_convictions, String, :tag => "FelonyConvictions"
|
54
|
+
element :highest_degree, String, :tag => "HighestDegree"
|
55
|
+
element :certifications, String, :tag => "Certifications"
|
56
|
+
element :motivation_to_change_jobs, String, :tag => "MotivationToChangeJobs"
|
57
|
+
element :employment_type, String, :tag => "EmploymentType"
|
58
|
+
element :last_updated, Time, :tag => "LastUpdated"
|
59
|
+
element :text, String, :tag => "ResumeText"
|
60
|
+
element :military_experience, String, :tag => "MilitaryExperience"
|
61
|
+
element :warning, String, :tag => "Warning"
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module CareerBuilder
|
2
|
+
|
3
|
+
module API
|
4
|
+
|
5
|
+
class ResumeSearch
|
6
|
+
|
7
|
+
include HappyMapper
|
8
|
+
|
9
|
+
tag 'Packet'
|
10
|
+
|
11
|
+
has_many :results, ResumeSearchResult
|
12
|
+
|
13
|
+
element :page_number, Integer, :tag => "PageNumber"
|
14
|
+
element :search_time, Time, :tag => "SearchTime"
|
15
|
+
element :hits, Integer, :tag => "Hits"
|
16
|
+
element :max_page, Integer, :tag => "MaxPage"
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CareerBuilder
|
2
|
+
|
3
|
+
module API
|
4
|
+
|
5
|
+
class ResumeSearchResult
|
6
|
+
|
7
|
+
include HappyMapper
|
8
|
+
|
9
|
+
tag 'ResumeResultItem_V3'
|
10
|
+
|
11
|
+
element :contact_email, String, :tag => "ContactEmail"
|
12
|
+
element :contact_name, String, :tag => "ContactName"
|
13
|
+
element :home_location, String, :tag => "HomeLocation"
|
14
|
+
element :last_update, Date, :tag => "LastUpdate"
|
15
|
+
element :title, String, :tag => "ResumeTitle"
|
16
|
+
element :job_title, String, :tag => "JobTitle"
|
17
|
+
element :recent_employer, String, :tag => "RecentEmployer"
|
18
|
+
element :recent_job_title, String, :tag => "RecentJobTitle"
|
19
|
+
element :recent_pay, Integer, :tag => "RecentPay"
|
20
|
+
element :id, String, :tag => "ResumeID"
|
21
|
+
element :user_did, String, :tag => "UserDID"
|
22
|
+
element :contact_email_md5, String, :tag => "ContactEmailMD5"
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CareerBuilder
|
2
|
+
|
3
|
+
module API
|
4
|
+
|
5
|
+
class School
|
6
|
+
|
7
|
+
include HappyMapper
|
8
|
+
|
9
|
+
element :name, String, :tag => "SchoolName"
|
10
|
+
element :major, String, :tag => "Major"
|
11
|
+
element :degree, String, :tag => "Degree"
|
12
|
+
element :graduation_date, String, :tag => "GraduationDate"
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CareerBuilder
|
2
|
+
|
3
|
+
class Client
|
4
|
+
|
5
|
+
attr_reader :email, :password, :session_token
|
6
|
+
|
7
|
+
def initialize(email, password)
|
8
|
+
@email, @password = email, password
|
9
|
+
end
|
10
|
+
|
11
|
+
def authenticate
|
12
|
+
@session_token = Requests::Authentication.new(self, :email => email, :password => password).perform
|
13
|
+
end
|
14
|
+
|
15
|
+
def authenticated?
|
16
|
+
!session_token.nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
def resumes(options = {})
|
20
|
+
Resume::LazyCollection.new(self, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def advanced_resume_search(options = {})
|
24
|
+
Requests::AdvancedResumeSearch.new(self, options).perform
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_resume(options = {})
|
28
|
+
Requests::GetResume.new(self, options).perform
|
29
|
+
end
|
30
|
+
|
31
|
+
def resume_actions_remaining_today(options = {})
|
32
|
+
Requests::ResumeActionsRemainingToday.new(self, options).perform
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module CareerBuilder
|
2
|
+
|
3
|
+
class Request
|
4
|
+
|
5
|
+
RESUME_SERVICE_ENDPOINT_URL = 'http://ws.careerbuilder.com/resumes/resumes.asmx'
|
6
|
+
|
7
|
+
attr_reader :options, :client
|
8
|
+
|
9
|
+
def initialize(client, options = {})
|
10
|
+
@client, @options = client, options
|
11
|
+
end
|
12
|
+
|
13
|
+
def perform
|
14
|
+
validate_options if defined?(self.class.const_get(:VALID_OPTIONS))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def validate_options
|
20
|
+
raise ArgumentError, "Invalid options #{invalid_options}" unless valid_options?
|
21
|
+
end
|
22
|
+
|
23
|
+
def invalid_options
|
24
|
+
(options.keys - self.class.const_get(:VALID_OPTIONS))
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid_options?
|
28
|
+
invalid_options.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def session_token
|
32
|
+
client.session_token
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_terrible_response(response)
|
36
|
+
xml_body = Nokogiri::XML(response.body) # not sure why I have to do it this way
|
37
|
+
inner_xml = xml_body.children.first
|
38
|
+
inner_xml.text
|
39
|
+
end
|
40
|
+
|
41
|
+
CUSTOM_KEY_TRANSFORMS = {
|
42
|
+
:resume_id => "ResumeID",
|
43
|
+
:account_did => "AccountDID"
|
44
|
+
}
|
45
|
+
|
46
|
+
def transform_key(key)
|
47
|
+
if custom_transform = CUSTOM_KEY_TRANSFORMS[key]
|
48
|
+
custom_transform
|
49
|
+
else
|
50
|
+
key.to_s.camelize
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def transform_key_value_to_tag(key, value)
|
55
|
+
"<#{transform_key(key)}>#{value}</#{transform_key(key)}>"
|
56
|
+
end
|
57
|
+
|
58
|
+
def transform_options_to_xml(options)
|
59
|
+
elements = []
|
60
|
+
|
61
|
+
# let's make sure SessionToken is always at the top of the request
|
62
|
+
if session_key = options.delete(:session_token)
|
63
|
+
elements << transform_key_value_to_tag(:session_token, session_token)
|
64
|
+
end
|
65
|
+
|
66
|
+
options.sort_by { |k, v| k.to_s }.each do |key, value|
|
67
|
+
elements << transform_key_value_to_tag(key, value)
|
68
|
+
end
|
69
|
+
|
70
|
+
elements.join
|
71
|
+
end
|
72
|
+
|
73
|
+
def perform_request(method, packet_contents)
|
74
|
+
parse_terrible_response Net::HTTP.post_form(URI.parse(RESUME_SERVICE_ENDPOINT_URL + "/#{method}"), 'Packet' => "<Packet>#{packet_contents}</Packet>")
|
75
|
+
rescue Errno::ECONNRESET
|
76
|
+
warn "The connection was reset, retrying"
|
77
|
+
retry
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module CareerBuilder
|
2
|
+
|
3
|
+
class Request::Authenticated < Request
|
4
|
+
|
5
|
+
def perform
|
6
|
+
super
|
7
|
+
require_authentication
|
8
|
+
options.merge!(:session_token => session_token) unless options.has_key?(:session_token)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def require_authentication
|
14
|
+
if !client.authenticated?
|
15
|
+
unless client.authenticate
|
16
|
+
raise InvalidCredentials
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module CareerBuilder
|
2
|
+
|
3
|
+
module Requests
|
4
|
+
|
5
|
+
class AdvancedResumeSearch < Request::Authenticated
|
6
|
+
|
7
|
+
# List of valid options available at:
|
8
|
+
# http://ws.careerbuilder.com/resumes/resumes.asmx/V2_AdvancedResumeSearch_ValidFields
|
9
|
+
#
|
10
|
+
VALID_OPTIONS = [:keywords, :search_pattern, :job_categories,
|
11
|
+
:city, :state, :zip_code, :country,
|
12
|
+
:search_radius_in_miles, :relocation_filter,
|
13
|
+
:freshness_in_days, :employment_type,
|
14
|
+
:minimum_experience,
|
15
|
+
:minimum_travel_requirement, :minimum_degree,
|
16
|
+
:compensation_type, :minimum_salary,
|
17
|
+
:maximum_salary,
|
18
|
+
:exclude_resumes_with_no_salary,
|
19
|
+
:languages_spoken, :currently_employed,
|
20
|
+
:management_experience,
|
21
|
+
:minimum_employees_managed, :maximum_commute,
|
22
|
+
:security_clearance, :work_status,
|
23
|
+
:exclude_ivr_resumes, :order_by, :page_number,
|
24
|
+
:rows_per_page, :cust_acct_code, :custom_xml,
|
25
|
+
:military_experience, :niche_inclusion,
|
26
|
+
:lemmatize, :job_title, :company, :school,
|
27
|
+
:rsadid, :cb_minimum_experience,
|
28
|
+
:cb_maximum_experience].freeze
|
29
|
+
|
30
|
+
def perform
|
31
|
+
super
|
32
|
+
response = perform_request("V2_AdvancedResumeSearch", transform_options_to_xml(options))
|
33
|
+
|
34
|
+
API::ResumeSearch.parse(response)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CareerBuilder
|
2
|
+
|
3
|
+
module Requests
|
4
|
+
|
5
|
+
class Authentication < Request
|
6
|
+
|
7
|
+
def perform
|
8
|
+
response = perform_request("BeginSessionV2", "<Email>#{options[:email]}</Email><Password>#{options[:password]}</Password>")
|
9
|
+
packet = Nokogiri::XML(response)
|
10
|
+
|
11
|
+
if session_token = packet.search("//SessionToken")
|
12
|
+
session_token_text = session_token.text
|
13
|
+
if session_token_text == "Invalid"
|
14
|
+
return nil
|
15
|
+
else
|
16
|
+
return session_token_text
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CareerBuilder
|
2
|
+
|
3
|
+
module Requests
|
4
|
+
|
5
|
+
class GetResume < Request::Authenticated
|
6
|
+
|
7
|
+
VALID_OPTIONS = [:resume_id, :cust_acct_code, :get_word_doc_if_available].freeze
|
8
|
+
|
9
|
+
def perform
|
10
|
+
super
|
11
|
+
response = perform_request("V2_GetResume", transform_options_to_xml(options))
|
12
|
+
|
13
|
+
if response =~ /ResumeID/ # valid response
|
14
|
+
API::Resume.parse(response, :single => true)
|
15
|
+
else
|
16
|
+
raise OutOfCredits
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module CareerBuilder
|
2
|
+
|
3
|
+
module Requests
|
4
|
+
|
5
|
+
class ResumeActionsRemainingToday < Request::Authenticated
|
6
|
+
|
7
|
+
VALID_OPTIONS = [:account_did]
|
8
|
+
|
9
|
+
def perform
|
10
|
+
super
|
11
|
+
response = perform_request("V2_ResumeActionsRemainingToday", transform_options_to_xml(options))
|
12
|
+
|
13
|
+
doc = Nokogiri::XML(response)
|
14
|
+
doc.xpath("//Packet").text.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|