workable 1.0.0 → 2.0.0rc1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile +1 -1
- data/Guardfile +3 -3
- data/README.md +5 -6
- data/Rakefile +3 -2
- data/lib/workable.rb +7 -4
- data/lib/workable/client.rb +106 -91
- data/lib/workable/collection.rb +29 -0
- data/lib/workable/errors.rb +6 -6
- data/lib/workable/transformation.rb +26 -0
- data/lib/workable/version.rb +1 -1
- data/spec/fixtures.rb +34 -122
- data/spec/fixtures/about.json +8 -0
- data/spec/fixtures/job.json +31 -0
- data/spec/fixtures/job_candidate.json +119 -0
- data/spec/fixtures/job_candidates.json +58 -0
- data/spec/fixtures/job_questions.json +34 -0
- data/spec/fixtures/jobs.json +73 -0
- data/spec/fixtures/members.json +32 -0
- data/spec/fixtures/new_candidate.json +60 -0
- data/spec/fixtures/new_candidate_response.json +109 -0
- data/spec/fixtures/recruiters.json +14 -0
- data/spec/fixtures/stages.json +40 -0
- data/spec/lib/workable/client_spec.rb +157 -121
- data/spec/lib/workable/collection_spec.rb +49 -0
- data/spec/lib/workable/transformation_spec.rb +38 -0
- data/spec/spec_helper.rb +6 -1
- data/workable.gemspec +7 -7
- metadata +32 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d886522592c7473bc8fb058f56ea68a8b2d79585
|
4
|
+
data.tar.gz: c019b51b0a908a38ac418fd2ea8003f98c5e9ca5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43d116e2475036166516674d0efff67bb5f8e13bd3f05bb0c63373ae91398df068d6190ac99f19f9bc662cd0d86755c7d3fbc752cafff4e177adbe9d888c1e4f
|
7
|
+
data.tar.gz: d9007195f31b8075e86a63a07bd3836a75ae2d6d1cc06e0ceccc9661212630f49e7a0f7e3c1d444f027f1824e7567afb7efada341279759fc405d7d77cb37de8
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## 2.0.0rc1
|
2
|
+
|
3
|
+
Date: 2015-11-02
|
4
|
+
|
5
|
+
**Breaking change - switched to v3 API**
|
6
|
+
|
7
|
+
https://workable.readme.io/docs/whats-new-in-v3
|
8
|
+
|
9
|
+
- Collections returned by workable API are now paginated - that enforced introducing
|
10
|
+
new `Workable::Collection` class that holds `data` (jobs/candidates) and reference
|
11
|
+
to next page of results.
|
12
|
+
|
13
|
+
- `jobs` method does not set default `stage` argument - please specify it explicitly!
|
14
|
+
Also - now it accepts a hash as more parameters are available in v3 API
|
15
|
+
|
1
16
|
## 1.0.0
|
2
17
|
|
3
18
|
Date: 2015-05-16
|
data/Gemfile
CHANGED
data/Guardfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
directories %w(lib spec)
|
2
2
|
|
3
|
-
guard :rspec, cmd:
|
3
|
+
guard :rspec, cmd: 'rspec' do
|
4
4
|
watch(%r{^spec/.+_spec\.rb$})
|
5
|
-
watch(%r{^lib/(.+)\.rb$})
|
6
|
-
watch(%r{spec/(spec_helper|fixtures)\.rb$})
|
5
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
6
|
+
watch(%r{spec/(spec_helper|fixtures)\.rb$}) { 'spec' }
|
7
7
|
end
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
Dead-simple Ruby API client for [workable.com][1]. No extra runtime dependencies. Ruby >= 1.9.3.
|
10
10
|
|
11
|
-
Uses
|
11
|
+
Uses v3 API provided by workable.
|
12
12
|
|
13
13
|
## Installation
|
14
14
|
|
@@ -28,18 +28,17 @@ Or install it yourself as:
|
|
28
28
|
|
29
29
|
## Usage
|
30
30
|
|
31
|
-
|
32
|
-
- fetch jobs
|
33
|
-
- fetch job details
|
34
|
-
- fetch candidates for given job
|
31
|
+
Gem covers all endpoints mentioned in official v3 workable API documentation (https://workable.readme.io/docs/).
|
35
32
|
|
36
33
|
### Example
|
37
34
|
|
35
|
+
For detailed documentation please refer to: http://www.rubydoc.info/gems/workable
|
36
|
+
|
38
37
|
``` ruby
|
39
38
|
client = Workable::Client.new(api_key: 'api_key', subdomain: 'your_subdomain')
|
40
39
|
|
41
40
|
# takes optional phase argument (string): 'published' (default), 'draft', 'closed' or 'archived'
|
42
|
-
client.jobs # =>
|
41
|
+
client.jobs # => Workable::Collection
|
43
42
|
|
44
43
|
shortcode = client.jobs.first["shortcode"]
|
45
44
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
require
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
require 'bundler/gem_tasks'
|
2
3
|
|
3
4
|
RSpec::Core::RakeTask.new('spec')
|
4
5
|
task default: :spec
|
5
6
|
|
6
7
|
task :console do
|
7
|
-
sh
|
8
|
+
sh 'irb -r ./lib/workable.rb'
|
8
9
|
end
|
9
10
|
|
10
11
|
task c: :console
|
data/lib/workable.rb
CHANGED
@@ -3,11 +3,14 @@ require 'uri'
|
|
3
3
|
require 'net/http'
|
4
4
|
require 'date'
|
5
5
|
require 'ostruct'
|
6
|
+
require 'cgi'
|
6
7
|
|
7
|
-
require_relative
|
8
|
-
require_relative
|
9
|
-
require_relative
|
8
|
+
require_relative 'workable/version'
|
9
|
+
require_relative 'workable/errors'
|
10
|
+
require_relative 'workable/client'
|
11
|
+
require_relative 'workable/transformation'
|
12
|
+
require_relative 'workable/collection'
|
10
13
|
|
11
14
|
module Workable
|
12
|
-
API_VERSION =
|
15
|
+
API_VERSION = 3
|
13
16
|
end
|
data/lib/workable/client.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
module Workable
|
2
2
|
class Client
|
3
|
-
|
4
3
|
# set access to workable and data transformation methods
|
5
4
|
#
|
6
5
|
# @param options [Hash]
|
7
6
|
# @option options :api_key [String] api key for workable
|
8
7
|
# @option options :subdomain [String] company subdomain in workable
|
9
8
|
# @option options :transform_to [Hash<Symbol: Proc>] mapping of transformations for data
|
10
|
-
# available transformations: [:job, :candidate, :question, :stage]
|
9
|
+
# available transformations: [:job, :candidate, :question, :stage, :recruiter, :member]
|
11
10
|
# when no transformation is given raw Hash / Array data is returned
|
12
11
|
#
|
13
12
|
# @example transformation for candidates using `MyApp::Candidate.find_and_update_or_create`
|
@@ -29,60 +28,103 @@ module Workable
|
|
29
28
|
# }
|
30
29
|
# )
|
31
30
|
def initialize(options = {})
|
32
|
-
@api_key = options.fetch(:api_key) {
|
33
|
-
@subdomain = options.fetch(:subdomain) {
|
34
|
-
@transform_to = options[:transform_to]
|
35
|
-
@transform_from = options[:transform_from]
|
31
|
+
@api_key = options.fetch(:api_key) { configuration_error 'Missing api_key argument' }
|
32
|
+
@subdomain = options.fetch(:subdomain) { configuration_error 'Missing subdomain argument' }
|
33
|
+
@transform_to = Transformation.new(options[:transform_to])
|
34
|
+
@transform_from = Transformation.new(options[:transform_from])
|
35
|
+
end
|
36
|
+
|
37
|
+
# return information about your account
|
38
|
+
def about
|
39
|
+
get_request('')
|
40
|
+
end
|
41
|
+
|
42
|
+
# returns a collection of your account members
|
43
|
+
def members
|
44
|
+
@transform_to.apply(:member, get_request('members')['members'])
|
45
|
+
end
|
46
|
+
|
47
|
+
# returns a collection of your account external recruiters
|
48
|
+
def recruiters
|
49
|
+
@transform_to.apply(:recruiter, get_request('recruiters')['recruiters'])
|
50
|
+
end
|
51
|
+
|
52
|
+
# returns a collection of your recruitment pipeline stages
|
53
|
+
def stages
|
54
|
+
@transform_to.apply(:stage, get_request('stages')['stages'])
|
36
55
|
end
|
37
56
|
|
38
|
-
# request jobs
|
39
|
-
# @
|
40
|
-
|
41
|
-
|
57
|
+
# request posted jobs
|
58
|
+
# @option params [Hash] optional filter parameters
|
59
|
+
# @option params :state [String] Returns jobs with the current state. Possible values (draft, published, archived & closed)
|
60
|
+
# @option params :limit [Integer] Specifies the number of jobs to try and retrieve per page
|
61
|
+
# @option params :since_id [String] Returns results with an ID more than or equal to the specified ID.
|
62
|
+
# @option params :max_id [String] Returns results with an ID less than or equal to the specified ID.
|
63
|
+
# @option params :created_after [Timestamp|Integer] Returns results created after the specified timestamp.
|
64
|
+
# @option params :updated_after [Timestamp|Integer] Returns results updated after the specified timestamp.
|
65
|
+
def jobs(params = {})
|
66
|
+
build_collection('jobs', :job, 'jobs', params)
|
42
67
|
end
|
43
68
|
|
44
69
|
# request detailed information about job
|
45
70
|
# @param shortcode [String] job short code
|
46
71
|
def job_details(shortcode)
|
47
|
-
transform_to(:job, get_request("jobs/#{shortcode}"))
|
72
|
+
@transform_to.apply(:job, get_request("jobs/#{shortcode}"))
|
73
|
+
end
|
74
|
+
|
75
|
+
# list of questions for job
|
76
|
+
# @param shortcode [String] job short code
|
77
|
+
def job_questions(shortcode)
|
78
|
+
@transform_to.apply(:question, get_request("jobs/#{shortcode}/questions")['questions'])
|
79
|
+
end
|
80
|
+
|
81
|
+
# return a collection of job's members
|
82
|
+
# @param shortcode [String] job short code
|
83
|
+
def job_members(shortcode)
|
84
|
+
@transform_to.apply(:member, get_request("jobs/#{shortcode}/members")['members'])
|
85
|
+
end
|
86
|
+
|
87
|
+
# return a collection of the job's external recruiters
|
88
|
+
# @param shortcode [String] job short code
|
89
|
+
def job_recruiters(shortcode)
|
90
|
+
@transform_to.apply(:recruiter, get_request("jobs/#{shortcode}/recruiters")['recruiters'])
|
48
91
|
end
|
49
92
|
|
50
93
|
# list candidates for given job
|
51
94
|
# @param shortcode [String] job shortcode to select candidates from
|
52
|
-
# @param
|
53
|
-
# @option
|
54
|
-
# @option
|
55
|
-
|
56
|
-
|
57
|
-
|
95
|
+
# @param params [Hash] extra options like `state` or `limit`
|
96
|
+
# @option params :state [String] optional state slug, if not given candidates are listed for all stages
|
97
|
+
# @option params :limit [Number|String] optional limit of candidates to download, if not given all candidates are listed
|
98
|
+
# @option params :since_id [String] Returns results with an ID more than or equal to the specified ID.
|
99
|
+
# @option params :max_id [String] Returns results with an ID less than or equal to the specified ID.
|
100
|
+
# @option params :created_after [Timestamp|Integer] Returns results created after the specified timestamp.
|
101
|
+
# @option params :updated_after [Timestamp|Integer] Returns results updated after the specified timestamp.
|
102
|
+
def job_candidates(shortcode, params = {})
|
103
|
+
build_collection("jobs/#{shortcode}/candidates", :candidate, 'candidates', params)
|
58
104
|
end
|
59
105
|
|
60
|
-
#
|
61
|
-
# @param shortcode [String] job
|
62
|
-
|
63
|
-
|
106
|
+
# return the full object of a specific candidate
|
107
|
+
# @param shortcode [String] job shortcode to select candidate from
|
108
|
+
# @param id [String] candidates's id
|
109
|
+
def job_candidate(shortcode, id)
|
110
|
+
@transform_to.apply(:candidate, get_request("jobs/#{shortcode}/candidates/#{id}")['candidate'])
|
64
111
|
end
|
65
112
|
|
66
113
|
# create new candidate for given job
|
67
114
|
# @param candidate [Hash] the candidate data as described in
|
68
|
-
#
|
115
|
+
# https://workable.readme.io/docs/job-candidates-create
|
69
116
|
# including the `{"candidate"=>{}}` part
|
70
117
|
# @param shortcode [String] job short code
|
71
118
|
# @param stage_slug [String] optional stage slug
|
72
119
|
# @return [Hash] the candidate information without `{"candidate"=>{}}` part
|
73
120
|
def create_job_candidate(candidate, shortcode, stage_slug = nil)
|
74
|
-
shortcode = "#{shortcode}/#{stage_slug}"
|
75
|
-
transform_to(:candidate, post_request("jobs/#{shortcode}/candidates", candidate)["candidate"])
|
76
|
-
end
|
121
|
+
shortcode = "#{shortcode}/#{stage_slug}" if stage_slug
|
77
122
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
123
|
+
response = post_request("jobs/#{shortcode}/candidates") do |request|
|
124
|
+
request.body = @transform_from.apply(:candidate, candidate).to_json
|
125
|
+
end
|
82
126
|
|
83
|
-
|
84
|
-
def recruiters
|
85
|
-
transform_to(:stage, get_request("recruiters")['recruiters'])
|
127
|
+
@transform_to.apply(:candidate, response['candidate'])
|
86
128
|
end
|
87
129
|
|
88
130
|
private
|
@@ -91,28 +133,30 @@ module Workable
|
|
91
133
|
|
92
134
|
# build the url to api
|
93
135
|
def api_url
|
94
|
-
|
136
|
+
'https://www.workable.com/spi/v%s/accounts/%s' % [Workable::API_VERSION, subdomain]
|
95
137
|
end
|
96
138
|
|
97
139
|
# do the get request to api
|
98
|
-
def get_request(url)
|
99
|
-
|
140
|
+
def get_request(url, params = {})
|
141
|
+
params = URI.encode_www_form(params.keep_if { |k, v| k && v })
|
142
|
+
full_url = params.empty? ? url : [url, params].join('?')
|
143
|
+
do_request(full_url, Net::HTTP::Get)
|
100
144
|
end
|
101
145
|
|
102
146
|
# do the post request to api
|
103
|
-
def post_request(url
|
147
|
+
def post_request(url)
|
104
148
|
do_request(url, Net::HTTP::Post) do |request|
|
105
|
-
request
|
149
|
+
yield(request) if block_given?
|
106
150
|
end
|
107
151
|
end
|
108
152
|
|
109
153
|
# generic part of requesting api
|
110
|
-
def do_request(url, type, &
|
154
|
+
def do_request(url, type, &_block)
|
111
155
|
uri = URI.parse("#{api_url}/#{url}")
|
112
156
|
http = Net::HTTP.new(uri.host, uri.port)
|
113
157
|
http.use_ssl = true
|
114
158
|
|
115
|
-
request
|
159
|
+
request = type.new(uri.request_uri, headers)
|
116
160
|
yield request if block_given?
|
117
161
|
response = http.request(request)
|
118
162
|
|
@@ -127,28 +171,26 @@ module Workable
|
|
127
171
|
when 200...300 # handled with response
|
128
172
|
JSON.parse(response.body)
|
129
173
|
when 401
|
130
|
-
|
174
|
+
fail Errors::NotAuthorized, JSON.parse(response.body)['error']
|
131
175
|
when 404
|
132
|
-
|
176
|
+
fail Errors::NotFound, JSON.parse(response.body)['error']
|
133
177
|
when 422
|
134
178
|
handle_response_422(response)
|
135
179
|
when 503
|
136
|
-
|
180
|
+
fail Errors::RequestToLong, response.body
|
137
181
|
else
|
138
|
-
|
182
|
+
fail Errors::InvalidResponse, "Response code: #{response.code} message: #{response.body}"
|
139
183
|
end
|
140
184
|
end
|
141
185
|
|
142
186
|
def handle_response_422(response)
|
143
187
|
data = JSON.parse(response.body)
|
144
|
-
if
|
145
|
-
|
146
|
-
|
147
|
-
data[
|
148
|
-
then
|
149
|
-
raise Errors::AlreadyExists, data["error"]
|
188
|
+
if data['validation_errors'] &&
|
189
|
+
data['validation_errors']['email'] &&
|
190
|
+
data['validation_errors']['email'].include?('candidate already exists')
|
191
|
+
fail Errors::AlreadyExists, data['error']
|
150
192
|
else
|
151
|
-
|
193
|
+
fail Errors::NotFound, data['error']
|
152
194
|
end
|
153
195
|
end
|
154
196
|
|
@@ -158,35 +200,10 @@ module Workable
|
|
158
200
|
'Accept' => 'application/json',
|
159
201
|
'Authorization' => "Bearer #{api_key}",
|
160
202
|
'Content-Type' => 'application/json',
|
161
|
-
'User-Agent' => 'Workable Ruby Client'
|
203
|
+
'User-Agent' => 'Workable Ruby Client'
|
162
204
|
}
|
163
205
|
end
|
164
206
|
|
165
|
-
# build url for fetching job candidates
|
166
|
-
# @param shortcode [String] job shortcode to select candidates from
|
167
|
-
# @param options [Hash] extra options like `stage_slug` or `limit`
|
168
|
-
# @option options :stage_slug [String] optional stage slug, if not given candidates are listed for all stages
|
169
|
-
# @option options :limit [Number|String] optional limit of candidates to download, if not given all candidates are listed
|
170
|
-
def build_job_candidates_url(shortcode, options)
|
171
|
-
if (stage_slug = options.delete(:stage))
|
172
|
-
then stage_slug = "/#{stage_slug}"
|
173
|
-
end
|
174
|
-
params =
|
175
|
-
if options.empty?
|
176
|
-
then ""
|
177
|
-
else "?#{options.map{|k,v| "#{k}=#{v}"}.join("&")}"
|
178
|
-
end
|
179
|
-
"jobs/#{shortcode}#{stage_slug}/candidates#{params}"
|
180
|
-
end
|
181
|
-
|
182
|
-
# transform result using given method if defined
|
183
|
-
# @param type [Symbol] type of the transformation, one of `[:job, :candidate, :question, :stage]`
|
184
|
-
# @param result [Hash|Array|nil] the value to transform, can be nothing, `Hash` of values or `Array` of `Hash`es
|
185
|
-
# @return transformed result if transformation exists for type, raw result otherwise
|
186
|
-
def transform_to(type, result)
|
187
|
-
transform(@transform_to[type], result)
|
188
|
-
end
|
189
|
-
|
190
207
|
# transform input using given method if defined
|
191
208
|
# @param type [Symbol] type of the transformation, only `[:candidate]` supported so far
|
192
209
|
# @param result [Hash|Array|nil] the value to transform, can be nothing, `Hash` of values or `Array` of `Hash`es
|
@@ -195,22 +212,20 @@ module Workable
|
|
195
212
|
transform(@transform_from[type], input)
|
196
213
|
end
|
197
214
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
when Array
|
209
|
-
data.map{|datas| transformation.call(datas) }
|
210
|
-
else
|
211
|
-
transformation.call(data)
|
212
|
-
end
|
215
|
+
def build_collection(url, transform_mapping, root_key, params = {})
|
216
|
+
url = url.gsub(/#{api_url}\/?/, '')
|
217
|
+
response = get_request(url, params)
|
218
|
+
|
219
|
+
Collection.new(
|
220
|
+
@transform_to.apply(transform_mapping, response[root_key]),
|
221
|
+
method(__callee__),
|
222
|
+
transform_mapping,
|
223
|
+
root_key,
|
224
|
+
response['paging'])
|
213
225
|
end
|
214
226
|
|
227
|
+
def configuration_error(message)
|
228
|
+
fail Errors::InvalidConfiguration, message
|
229
|
+
end
|
215
230
|
end
|
216
231
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Workable
|
2
|
+
class Collection
|
3
|
+
extend Forwardable
|
4
|
+
def_delegators :@data, :size, :each, :[], :map, :first
|
5
|
+
|
6
|
+
attr_reader :data
|
7
|
+
|
8
|
+
def initialize(data, next_page_method, transform_mapping, root_key, paging = nil)
|
9
|
+
@data = data
|
10
|
+
|
11
|
+
if paging
|
12
|
+
@next_page = paging['next']
|
13
|
+
@next_page_method = next_page_method
|
14
|
+
@transform_mapping = transform_mapping
|
15
|
+
@root_key = root_key
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def next_page?
|
20
|
+
!! @next_page
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch_next_page
|
24
|
+
return unless next_page?
|
25
|
+
|
26
|
+
@next_page_method.call(@next_page, @transform_mapping, @root_key)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/workable/errors.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module Workable
|
2
2
|
module Errors
|
3
|
-
class WorkableError
|
3
|
+
class WorkableError < StandardError; end
|
4
4
|
class InvalidConfiguration < WorkableError; end
|
5
|
-
class NotAuthorized
|
6
|
-
class InvalidResponse
|
7
|
-
class NotFound
|
8
|
-
class AlreadyExists
|
9
|
-
class RequestToLong
|
5
|
+
class NotAuthorized < WorkableError; end
|
6
|
+
class InvalidResponse < WorkableError; end
|
7
|
+
class NotFound < WorkableError; end
|
8
|
+
class AlreadyExists < WorkableError; end
|
9
|
+
class RequestToLong < WorkableError; end
|
10
10
|
end
|
11
11
|
end
|