workable 0.2.0 → 1.0.0.rc1

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