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.
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