career_builder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/Gemfile +10 -0
  4. data/LICENSE +20 -0
  5. data/README.md +91 -0
  6. data/Rakefile +48 -0
  7. data/VERSION +1 -0
  8. data/career_builder.gemspec +95 -0
  9. data/lib/career_builder.rb +39 -0
  10. data/lib/career_builder/api/company.rb +17 -0
  11. data/lib/career_builder/api/interest.rb +16 -0
  12. data/lib/career_builder/api/job_type.rb +15 -0
  13. data/lib/career_builder/api/language.rb +15 -0
  14. data/lib/career_builder/api/location.rb +19 -0
  15. data/lib/career_builder/api/pay.rb +16 -0
  16. data/lib/career_builder/api/resume.rb +67 -0
  17. data/lib/career_builder/api/resume_search.rb +22 -0
  18. data/lib/career_builder/api/resume_search_result.rb +28 -0
  19. data/lib/career_builder/api/school.rb +18 -0
  20. data/lib/career_builder/api/shift_preference.rb +15 -0
  21. data/lib/career_builder/api/word_document.rb +16 -0
  22. data/lib/career_builder/client.rb +37 -0
  23. data/lib/career_builder/errors.rb +7 -0
  24. data/lib/career_builder/request.rb +82 -0
  25. data/lib/career_builder/request/authenticated.rb +23 -0
  26. data/lib/career_builder/requests/advanced_resume_search.rb +41 -0
  27. data/lib/career_builder/requests/authentication.rb +25 -0
  28. data/lib/career_builder/requests/get_resume.rb +24 -0
  29. data/lib/career_builder/requests/resume_actions_remaining_today.rb +21 -0
  30. data/lib/career_builder/resume.rb +44 -0
  31. data/lib/career_builder/resume/lazy_collection.rb +39 -0
  32. data/spec/career_builder/client_spec.rb +554 -0
  33. data/spec/career_builder/resume/lazy_collection_spec.rb +30 -0
  34. data/spec/career_builder/resume_spec.rb +5 -0
  35. data/spec/career_builder_spec.rb +5 -0
  36. data/spec/spec.opts +1 -0
  37. data/spec/spec_helper.rb +13 -0
  38. data/spec/support/webmock.rb +7 -0
  39. 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,15 @@
1
+ module CareerBuilder
2
+
3
+ module API
4
+
5
+ class ShiftPreference
6
+
7
+ include HappyMapper
8
+
9
+ element :text, String, :tag => "string"
10
+
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,16 @@
1
+ module CareerBuilder
2
+
3
+ module API
4
+
5
+ class WordDocument
6
+
7
+ include HappyMapper
8
+
9
+ element :filename, String, :tag => "FileName"
10
+ element :base64_date, String, :tag => "Base64Data"
11
+
12
+ end
13
+
14
+ end
15
+
16
+ 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,7 @@
1
+ module CareerBuilder
2
+
3
+ Error = Class.new(StandardError)
4
+ InvalidCredentials = Class.new(Error)
5
+ OutOfCredits = Class.new(Error)
6
+
7
+ 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