career_builder 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|