workable 0.2.0 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7eaebf59a51c6219ca5f278a91b99226b043fa1d
4
- data.tar.gz: f41c7493529160c2d7e134b0aeb1d3284b5218e2
3
+ metadata.gz: a09a961ce4873bd3a9f5d5ca68f74a7332af0c71
4
+ data.tar.gz: 613aae7f8c29374abcb0fd1b6dfb1043d4dfdeec
5
5
  SHA512:
6
- metadata.gz: 840612f71285030a314ab414b8facd077e0cbc8eac8076353a8ab4a4b835c954c9000d9454ceda93b19b0cf97b7154fad33e95ff109a27f2d72196217b3a8cd7
7
- data.tar.gz: 3231f81122c3170a0d461eed5c95733b0cd9fe85381ced8834eacb1988d873bcf65e7e5497d21f1e5bdab5e72b6eec72afd05bccb6837b63c80c4ff924cb6172
6
+ metadata.gz: 58fff5454c71ac7eaed87a20f34273d66a5ba86050686c8c28c95fbb0b3d128073fb41904cbb63d81f05bdb5ad5ed1c7b9ed2bbb920b51f585947b1dea8a66de
7
+ data.tar.gz: 2bc4952238a7c751c75480d7f70898a51b73a6cf6bee812f65f0a373807c00b8f4eec67327f24f1a763c2016cd873f1e051383237f187ba6f72f6defec76b00c
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /*.gem
10
11
  *.bundle
11
12
  *.so
12
13
  *.o
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ #ruby=ruby-2.2.2
4
+
3
5
  # Specify your gem's dependencies in workable-client.gemspec
4
6
  gemspec
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ directories %w(lib spec)
2
+
3
+ guard :rspec, cmd: "rspec" do
4
+ watch(%r{^spec/.+_spec\.rb$})
5
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
6
+ watch(%r{spec/(spec_helper|fixtures)\.rb$}) { "spec" }
7
+ end
data/README.md CHANGED
@@ -33,22 +33,28 @@ Internal interface / api is in early stage, so far you can:
33
33
  - fetch job details
34
34
  - fetch candidates for given job
35
35
 
36
- **Example:**
36
+ ### Example
37
37
 
38
38
  ``` ruby
39
39
  client = Workable::Client.new(api_key: 'api_key', subdomain: 'your_subdomain')
40
40
 
41
41
  # takes optional phase argument (string): 'published' (default), 'draft', 'closed' or 'archived'
42
- client.jobs # => [#<Workable::Job>, #<Workable::Job>]
42
+ client.jobs # => Array of hashes
43
43
 
44
- shortcode = 'job_shortcode'
44
+ shortcode = client.jobs.first["shortcode"]
45
45
 
46
46
  # API queries are not cached (at all) - it's up to you to cache results one way or another
47
47
 
48
- client.stages # => Array of hashes
49
- client.job_details(shortcode) # => #<Workable::Job>
50
- client.job_questions(shortcode) # => Array of hashes
51
- client.job_candidates(shortcode, stage_slug) # => Array of hashes (skip stage_slug for all stages)
48
+ client.stages # => Array of hashes
49
+ client.recruiters # => Array of hashes
50
+ client.job_details(shortcode) # => Hash
51
+ client.job_questions(shortcode) # => Array of hashes
52
+ client.job_candidates(shortcode, stage_slug) # => Array of hashes (stage_slug is optional)
53
+
54
+ # Adding candidates - candidate is a Hash as described in:
55
+ # http://resources.workable.com/add-candidates-using-api
56
+
57
+ client.create_job_candidate(candidate, shortcode, stage_slug) # => Hash (stage_slug is optional)
52
58
 
53
59
  # Possible errors (each one inherits from Workable::Errors::WorkableError)
54
60
  Workable::Errors::InvalidConfiguration # missing api_key / subdomain
@@ -58,14 +64,39 @@ Workable::Errors::NotFound # 404 from workable
58
64
  Workable::Errors::RequestToLong # When the requested result takes to long to calculate, try limiting your query
59
65
  ```
60
66
 
61
- ## Missing/Todo
67
+ ## Transformations
68
+
69
+ When creating `Client` you can specify extra methods/`Proc`s for
70
+ automated transformation of results and input.
71
+
72
+ ### Example
73
+
74
+ ```ruby
75
+ client = Workable::Client.new(
76
+ api_key: 'api_key',
77
+ subdomain: 'your_subdomain'
78
+ transform_to: {
79
+ candidate: OpenStruct.method(:new)
80
+ }
81
+ transform_from: {
82
+ candidate: Proc.new { |input| input.to_h }
83
+ }
84
+ )
85
+ ```
86
+
87
+ The first transformation `to` will make the `Client` return
88
+ `OpenStruct.new(result)` instead of just plain `result` everywhere where
89
+ candidate is expected. In case of Arrays the transformation will be
90
+ applied to every element.
62
91
 
63
- Pull requests are welcomed. So far this gem does not provide:
92
+ The second transformation `from` will expect an instance of `OpenStruct`
93
+ instead of raw data and will execute the `to_h` on the instance before
94
+ sending it to workable API.
64
95
 
65
- - candidates import
66
- - some sane way for parsing candidates json response
96
+ The rest of result will be returned as `Array`s/`Hash`es,
97
+ no transformation will be applied if not defined - raw data will be
98
+ returned.
67
99
 
68
- _(Personally I don't really need/use it)_
69
100
 
70
101
  ## Contributing
71
102
 
data/Rakefile CHANGED
@@ -1,4 +1,3 @@
1
- require "bundler/gem_tasks"
2
1
  require "rspec/core/rake_task"
3
2
 
4
3
  RSpec::Core::RakeTask.new('spec')
@@ -1,74 +1,182 @@
1
1
  module Workable
2
2
  class Client
3
+
4
+ # set access to workable and data transformation methods
5
+ #
6
+ # @param options [Hash]
7
+ # @option options :api_key [String] api key for workable
8
+ # @option options :subdomain [String] company subdomain in workable
9
+ # @option options :transform_to [Hash<Symbol: Proc>] mapping of transformations for data
10
+ # available transformations: [:job, :candidate, :question, :stage]
11
+ # when no transformation is given raw Hash / Array data is returned
12
+ #
13
+ # @example transformation for candidates using `MyApp::Candidate.find_and_update_or_create`
14
+ # client = Workable::Client.new(
15
+ # api_key: 'api_key',
16
+ # subdomain: 'your_subdomain',
17
+ # transform_to: {
18
+ # candidate: &MyApp::Candidate.method(:find_and_update_or_create)
19
+ # }
20
+ # )
21
+ #
22
+ # @example Linkedin gem style with Mash
23
+ # require "hashie"
24
+ # client = Workable::Client.new(
25
+ # api_key: 'api_key',
26
+ # subdomain: 'your_subdomain',
27
+ # transform_to: {
28
+ # candidate: &Hashie::Mash.method(:new)
29
+ # }
30
+ # )
3
31
  def initialize(options = {})
4
32
  @api_key = options.fetch(:api_key) { fail Errors::InvalidConfiguration, "Missing api_key argument" }
5
33
  @subdomain = options.fetch(:subdomain) { fail Errors::InvalidConfiguration, "Missing subdomain argument" }
34
+ @transform_to = options[:transform_to] || {}
35
+ @transform_from = options[:transform_from] || {}
6
36
  end
7
37
 
38
+ # request jobs of given type
39
+ # @param type [String] type of jobs to fetch, `published` by default
8
40
  def jobs(type = 'published')
9
- get_request("jobs?phase=#{type}")['jobs'].map do |params|
10
- Job.new(params)
11
- end
41
+ transform_to(:job, get_request("jobs?phase=#{type}")['jobs'])
12
42
  end
13
43
 
44
+ # request detailed information about job
45
+ # @param shortcode [String] job short code
14
46
  def job_details(shortcode)
15
- Job.new(get_request"jobs/#{shortcode}")
47
+ transform_to(:job, get_request("jobs/#{shortcode}"))
16
48
  end
17
49
 
50
+ # list candidates for given job
51
+ # @param shortcode [String] job shortcode to select candidates from
52
+ # @param stage_slug [String|nil] optional stage slug, if not given candidates are listed for all stages
18
53
  def job_candidates(shortcode, stage_slug = nil)
19
54
  shortcode = "#{shortcode}/#{stage_slug}" unless stage_slug.nil?
20
- get_request("jobs/#{shortcode}/candidates")['candidates']
55
+ transform_to(:candidate, get_request("jobs/#{shortcode}/candidates")['candidates'])
21
56
  end
22
57
 
58
+ # list of questions for job
59
+ # @param shortcode [String] job short code
23
60
  def job_questions(shortcode)
24
- get_request("jobs/#{shortcode}/questions")['questions']
61
+ transform_to(:question, get_request("jobs/#{shortcode}/questions")['questions'])
62
+ end
63
+
64
+ # create new candidate for given job
65
+ # @param candidate [Hash] the candidate data as described in
66
+ # http://resources.workable.com/add-candidates-using-api
67
+ # including the `{"candidate"=>{}}` part
68
+ # @param shortcode [String] job short code
69
+ # @param stage_slug [String] optional stage slug
70
+ # @return [Hash] the candidate information without `{"candidate"=>{}}` part
71
+ def create_job_candidate(candidate, shortcode, stage_slug = nil)
72
+ shortcode = "#{shortcode}/#{stage_slug}" unless stage_slug.nil?
73
+ transform_to(:candidate, post_request("jobs/#{shortcode}/candidates", candidate)["candidate"])
25
74
  end
26
75
 
76
+ # list of stages defined for company
27
77
  def stages
28
- get_request("stages")['stages']
78
+ transform_to(:stage, get_request("stages")['stages'])
79
+ end
80
+
81
+ # list of external recruiters for company
82
+ def recruiters
83
+ transform_to(:stage, get_request("recruiters")['recruiters'])
29
84
  end
30
85
 
31
86
  private
32
87
 
33
88
  attr_reader :api_key, :subdomain
34
89
 
90
+ # build the url to api
35
91
  def api_url
36
92
  "https://www.workable.com/spi/v%s/accounts/%s" % [Workable::API_VERSION, subdomain]
37
93
  end
38
94
 
95
+ # do the get request to api
39
96
  def get_request(url)
40
- uri = URI.parse("#{api_url}/#{url}")
97
+ do_request(url, Net::HTTP::Get)
98
+ end
99
+
100
+ # do the post request to api
101
+ def post_request(url, data)
102
+ do_request(url, Net::HTTP::Post) do |request|
103
+ request.body = transform_from(:candidate, data).to_json
104
+ end
105
+ end
41
106
 
107
+ # generic part of requesting api
108
+ def do_request(url, type, &block)
109
+ uri = URI.parse("#{api_url}/#{url}")
42
110
  http = Net::HTTP.new(uri.host, uri.port)
43
111
  http.use_ssl = true
44
112
 
45
- request = Net::HTTP::Get.new(uri.request_uri, headers)
113
+ request = type.new(uri.request_uri, headers)
114
+ yield request if block_given?
46
115
  response = http.request(request)
47
116
 
48
- validate!(response)
49
-
50
- JSON.parse(response.body)
117
+ parse!(response)
51
118
  end
52
119
 
53
- def validate!(response)
120
+ # parse the api response
121
+ def parse!(response)
54
122
  case response.code.to_i
123
+ when 204, 205
124
+ nil
125
+ when 200...300
126
+ JSON.parse(response.body)
55
127
  when 401
56
128
  raise Errors::NotAuthorized, response.body
57
129
  when 404
58
130
  raise Errors::NotFound, response.body
59
131
  when 503
60
132
  raise Errors::RequestToLong, response.body
61
- when proc { |code| code != 200 }
62
- raise Errors::InvalidResponse, "Response code: #{response.code}"
133
+ else
134
+ raise Errors::InvalidResponse, "Response code: #{response.code} message: #{response.body}"
63
135
  end
64
136
  end
65
137
 
138
+ # default headers for authentication and JSON support
66
139
  def headers
67
140
  {
68
- 'Accept' => 'application/json',
141
+ 'Accept' => 'application/json',
69
142
  'Authorization' => "Bearer #{api_key}",
70
- 'User-Agent' => 'Workable Ruby Client'
143
+ 'Content-Type' => 'application/json',
144
+ 'User-Agent' => 'Workable Ruby Client',
71
145
  }
72
146
  end
147
+
148
+ # transform result using given method if defined
149
+ # @param type [Symbol] type of the transformation, one of `[:job, :candidate, :question, :stage]`
150
+ # @param result [Hash|Array|nil] the value to transform, can be nothing, `Hash` of values or `Array` of `Hash`es
151
+ # @return transformed result if transformation exists for type, raw result otherwise
152
+ def transform_to(type, result)
153
+ transform(@transform_to[type], result)
154
+ end
155
+
156
+ # transform input using given method if defined
157
+ # @param type [Symbol] type of the transformation, only `[:candidate]` supported so far
158
+ # @param result [Hash|Array|nil] the value to transform, can be nothing, `Hash` of values or `Array` of `Hash`es
159
+ # @return transformed input if transformation exists for type, raw input otherwise
160
+ def transform_from(type, input)
161
+ transform(@transform_from[type], input)
162
+ end
163
+
164
+ # selects transformation strategy based on the inputs
165
+ # @param transformation [Method|Proc|nil] the transformation to perform
166
+ # @param data [Hash|Array|nil] the data to transform
167
+ # @return [Object|nil]
168
+ # results of the transformation if given, raw data otherwise
169
+ def transform(transformation, data)
170
+ return data unless transformation
171
+ case data
172
+ when nil
173
+ data
174
+ when Array
175
+ data.map{|datas| transformation.call(datas) }
176
+ else
177
+ transformation.call(data)
178
+ end
179
+ end
180
+
73
181
  end
74
182
  end
@@ -1,3 +1,3 @@
1
1
  module Workable
2
- VERSION = "0.2.0"
2
+ VERSION = "1.0.0.rc1"
3
3
  end
data/lib/workable.rb CHANGED
@@ -7,7 +7,6 @@ require 'ostruct'
7
7
  require_relative "workable/version"
8
8
  require_relative "workable/errors"
9
9
  require_relative "workable/client"
10
- require_relative "workable/job"
11
10
 
12
11
  module Workable
13
12
  API_VERSION = "2".freeze
@@ -47,7 +47,7 @@ describe Workable::Client do
47
47
  stub_request(:get, "https://www.workable.com/spi/v2/accounts/subdomain/jobs/03FF356C8B")
48
48
  .to_return(status: 200, body: job_json_fixture, headers: {})
49
49
 
50
- expect(client.job_details('03FF356C8B')).to be_kind_of(Workable::Job)
50
+ expect(client.job_details('03FF356C8B')).to be_kind_of(Hash)
51
51
  end
52
52
 
53
53
  it 'raises an exception when job is not found' do
@@ -100,4 +100,66 @@ describe Workable::Client do
100
100
  end
101
101
  end
102
102
 
103
+ describe "#transform_to" do
104
+ let(:client){
105
+ described_class.new(
106
+ api_key: 'test',
107
+ subdomain: 'subdomain',
108
+ transform_to: {
109
+ candidate: OpenStruct.method(:new)
110
+ }
111
+ )
112
+ }
113
+
114
+ it "transforms candidate" do
115
+ result = client.send(:transform_to, :candidate, {:name => "Tom"})
116
+ expect(result).to be_kind_of(OpenStruct)
117
+ expect(result.name).to eq("Tom")
118
+ end
119
+ end
120
+
121
+ describe "#transform_from" do
122
+ let(:client){
123
+ described_class.new(
124
+ api_key: 'test',
125
+ subdomain: 'subdomain',
126
+ transform_from: {
127
+ candidate: lambda { |input| input.marshal_dump }
128
+ }
129
+ )
130
+ }
131
+
132
+ it "transforms candidate" do
133
+ input = client.send(:transform_from, :candidate, OpenStruct.new({:name => "Tom"}))
134
+ expect(input).to be_kind_of(Hash)
135
+ expect(input[:name]).to eq("Tom")
136
+ end
137
+ end
138
+
139
+ describe "#transform" do
140
+ let(:client){ described_class.new(api_key: 'test', subdomain: 'subdomain') }
141
+
142
+ it "transforms one" do
143
+ result = client.send(:transform, OpenStruct.method(:new), {:name => "Tom"})
144
+ expect(result).to be_kind_of(OpenStruct)
145
+ expect(result.name).to eq("Tom")
146
+ end
147
+
148
+ it "transforms many" do
149
+ data = client.send(:transform, OpenStruct.method(:new), [{:name => "Tom"}, {:name => "Alice"}])
150
+ expect(data).to be_kind_of(Array)
151
+ expect(data.map(&:class)).to eq([OpenStruct, OpenStruct])
152
+ end
153
+
154
+ it "does not transform nil" do
155
+ data = client.send(:transform, OpenStruct.method(:new), nil)
156
+ expect(data).to eq(nil)
157
+ end
158
+
159
+ it "does not transform without transformation" do
160
+ data = client.send(:transform, nil, OpenStruct.new({:slug => "sourced"}))
161
+ expect(data).to eq(OpenStruct.new({:slug => "sourced"}))
162
+ end
163
+ end
164
+
103
165
  end
data/workable.gemspec CHANGED
@@ -6,8 +6,8 @@ require 'workable/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "workable"
8
8
  spec.version = Workable::VERSION
9
- spec.authors = ["Rafał Wojsznis"]
10
- spec.email = ["rafal.wojsznis@gmail.com"]
9
+ spec.authors = ["Rafał Wojsznis", "Michal Papis"]
10
+ spec.email = ["rafal.wojsznis@gmail.com", "mpapis@gmail.com"]
11
11
  spec.homepage = "https://github.com/emq/workable"
12
12
  spec.license = "MIT"
13
13
  spec.summary = spec.description = "Dead-simple Ruby API client for workable.com"
@@ -19,9 +19,10 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.required_ruby_version = '>= 1.9.3'
21
21
 
22
- spec.add_development_dependency 'bundler', '~> 1.7'
23
22
  spec.add_development_dependency 'rake', '~> 10.0'
24
23
  spec.add_development_dependency 'rspec', '~> 3.1.0'
25
24
  spec.add_development_dependency 'webmock', '~> 1.20.4'
26
25
  spec.add_development_dependency 'coveralls', '~> 0.7.2'
26
+ spec.add_development_dependency 'guard', '~> 2.12'
27
+ spec.add_development_dependency 'guard-rspec', '~> 4.5'
27
28
  end
metadata CHANGED
@@ -1,29 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafał Wojsznis
8
+ - Michal Papis
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2015-03-17 00:00:00.000000000 Z
12
+ date: 2015-04-16 00:00:00.000000000 Z
12
13
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.7'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.7'
27
14
  - !ruby/object:Gem::Dependency
28
15
  name: rake
29
16
  requirement: !ruby/object:Gem::Requirement
@@ -80,9 +67,38 @@ dependencies:
80
67
  - - "~>"
81
68
  - !ruby/object:Gem::Version
82
69
  version: 0.7.2
70
+ - !ruby/object:Gem::Dependency
71
+ name: guard
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '2.12'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '2.12'
84
+ - !ruby/object:Gem::Dependency
85
+ name: guard-rspec
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '4.5'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '4.5'
83
98
  description: Dead-simple Ruby API client for workable.com
84
99
  email:
85
100
  - rafal.wojsznis@gmail.com
101
+ - mpapis@gmail.com
86
102
  executables: []
87
103
  extensions: []
88
104
  extra_rdoc_files: []
@@ -92,17 +108,16 @@ files:
92
108
  - ".travis.yml"
93
109
  - CHANGELOG.md
94
110
  - Gemfile
111
+ - Guardfile
95
112
  - LICENSE.txt
96
113
  - README.md
97
114
  - Rakefile
98
115
  - lib/workable.rb
99
116
  - lib/workable/client.rb
100
117
  - lib/workable/errors.rb
101
- - lib/workable/job.rb
102
118
  - lib/workable/version.rb
103
119
  - spec/fixtures.rb
104
120
  - spec/lib/workable/client_spec.rb
105
- - spec/lib/workable/job_spec.rb
106
121
  - spec/spec_helper.rb
107
122
  - workable.gemspec
108
123
  homepage: https://github.com/emq/workable
@@ -120,9 +135,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
120
135
  version: 1.9.3
121
136
  required_rubygems_version: !ruby/object:Gem::Requirement
122
137
  requirements:
123
- - - ">="
138
+ - - ">"
124
139
  - !ruby/object:Gem::Version
125
- version: '0'
140
+ version: 1.3.1
126
141
  requirements: []
127
142
  rubyforge_project:
128
143
  rubygems_version: 2.4.6
@@ -132,5 +147,4 @@ summary: Dead-simple Ruby API client for workable.com
132
147
  test_files:
133
148
  - spec/fixtures.rb
134
149
  - spec/lib/workable/client_spec.rb
135
- - spec/lib/workable/job_spec.rb
136
150
  - spec/spec_helper.rb
data/lib/workable/job.rb DELETED
@@ -1,26 +0,0 @@
1
- module Workable
2
- class Job
3
- # from main jobs query
4
- attr_reader :key, :title, :full_title, :code, :shortcode, :state,
5
- :department, :url, :application_url, :shortlink, :location
6
-
7
- # from job details
8
- attr_reader :full_description, :description, :requirements, :benefits,
9
- :employment_type, :industry, :function, :experience, :education
10
-
11
- def initialize(params)
12
- params.each do |key, value|
13
- value = OpenStruct.new(value) if value.is_a?(Hash)
14
- instance_variable_set("@#{key}", value)
15
- end
16
- end
17
-
18
- def location_name
19
- "#{location.city}, #{location.country}"
20
- end
21
-
22
- def created_at
23
- Date.parse(@created_at)
24
- end
25
- end
26
- end
@@ -1,10 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Workable::Job do
4
- it 'parses json response' do
5
- job = described_class.new(JSON.parse(job_json_fixture))
6
-
7
- expect(job.location.country_code).to eq 'PL'
8
- expect(job.created_at).to be_kind_of(Date)
9
- end
10
- end