ashby 0.1.3

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.
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ # Ashby::CustomFields manages setting custom field values for objects
5
+ # in the Ashby API, such as jobs or candidates.
6
+ #
7
+ # This class provides a method to assign a specific value to a custom field
8
+ # identified by field ID, for a particular object type and ID.
9
+ #
10
+ # Example:
11
+ # Ashby::CustomFields.set_field(
12
+ # object_id: 'job_abc123',
13
+ # object_type: 'job',
14
+ # field_id: 'cf_custom_location',
15
+ # field_value: 'Remote'
16
+ # )
17
+ #
18
+ class CustomFields < Client
19
+ def self.set_field(object_id: nil, object_type: nil, field_id: nil, field_value: nil)
20
+ raise ArgumentError, '`object_id` is required' if object_id.blank?
21
+ raise ArgumentError, '`object_type` is required' if object_type.blank?
22
+ raise ArgumentError, '`field_id` is required' if field_id.blank?
23
+ raise ArgumentError, '`field_value` is required' if field_value.blank?
24
+
25
+ payload = {
26
+ objectId: object_id,
27
+ objectType: object_type,
28
+ fieldId: field_id,
29
+ fieldValue: field_value
30
+ }
31
+
32
+ post('customField.setValue', payload)['results']
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ # Ashby::Departments provides methods to interact with the
5
+ # Ashby API's department-related endpoints.
6
+ #
7
+ # Includes fetching a list of departments and retrieving a specific department by ID.
8
+ #
9
+ # Example:
10
+ # Ashby::Departments.all
11
+ # Ashby::Departments.find(id: 'abc123')
12
+ #
13
+ class Departments < Client
14
+ # Fetches all departments
15
+ def self.all
16
+ response = post('department.list')
17
+ response['results']
18
+ end
19
+
20
+ # Finds a department by its Ashby ID
21
+ def self.find(id: nil)
22
+ raise ArgumentError, 'Department ID is required' if id.to_s.strip.empty?
23
+
24
+ payload = { departmentId: id }
25
+ response = post('department.info', payload)
26
+ response['results']
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ # Custom error class for the Ashby module that extends StandardError
5
+ # with optional error code support.
6
+ #
7
+ # This class provides a standardized way to handle errors within the Ashby
8
+ # module by allowing both error messages and optional error codes to be
9
+ # associated with exceptions.
10
+ #
11
+ # @example Basic usage with message only
12
+ # raise Ashby::Error.new("Something went wrong")
13
+ #
14
+ # @example Usage with message and error code
15
+ # raise Ashby::Error.new("API request failed", 500)
16
+ #
17
+ # @example Accessing the error code
18
+ # begin
19
+ # raise Ashby::Error.new("Not found", 404)
20
+ # rescue Ashby::Error => e
21
+ # puts e.message # => "Not found"
22
+ # puts e.code # => 404
23
+ # end
24
+ #
25
+ # @attr_reader [Object, nil] code The optional error code associated with this error
26
+ class Error < StandardError
27
+ attr_reader :code
28
+
29
+ # Initialize a new Ashby::Error instance
30
+ #
31
+ # @param message [String] The error message
32
+ # @param code [Object, nil] Optional error code (typically an integer, but can be any object)
33
+ def initialize(message, code = nil)
34
+ super(message)
35
+ @code = code
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ #
5
+ # Ashby::Feedback provides access to feedback-related operations via the Ashby API.
6
+ # # Supported functionality includes:
7
+ # - Retrieving a paginated list of all feedback
8
+ # # Example usage:
9
+ # Ashby::Feedback.all
10
+ #
11
+ class Feedback < Client
12
+ # Fetches all feedback with pagination support
13
+ def self.all(payload = {})
14
+ paginated_post('applicationFeedback.list', payload)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ # Ashby::HiringTeams provides access to hiring team management functionality in the Ashby API.
5
+ #
6
+ # Includes support for:
7
+ # - Adding a member to a hiring team
8
+ # - Removing a member from a hiring team
9
+ #
10
+ # Example usage:
11
+ # Ashby::HiringTeams.add_hiring_team_member(type: :job, id: 'job_123', member_id: 'user_456', role_id: 'role_789')
12
+ # Ashby::HiringTeams.remove_hiring_team_member(type: :application, id: 'app_123', member_id: 'user_456',
13
+ # role_id: 'role_789')
14
+ #
15
+ class HiringTeams < Client
16
+ def self.remove_hiring_team_member(type, id, member_id, role_id)
17
+ payload = build_hiring_team_payload(type, id, member_id, role_id)
18
+ response = post('hiringTeam.removeMember', payload)
19
+ response['results']
20
+ end
21
+
22
+ def self.add_hiring_team_member(type, id, member_id, role_id)
23
+ payload = build_hiring_team_payload(type, id, member_id, role_id)
24
+ response = post('hiringTeam.addMember', payload)
25
+ response['results']
26
+ end
27
+
28
+ def self.build_hiring_team_payload(type, id, member_id, role_id) # rubocop:disable Metrics/MethodLength
29
+ payload = {}
30
+ case type
31
+ when :job
32
+ payload[:jobId] = id.to_s if id
33
+ when :application
34
+ payload[:applicationId] = id.to_s
35
+ when :opening
36
+ payload[:openingId] = id.to_s
37
+ else
38
+ raise ArgumentError, "Invalid type: #{type}. Must be one of :job, :application, or :opening."
39
+ end
40
+ payload[:teamMemberId] = member_id if member_id
41
+ payload[:roleId] = role_id if role_id
42
+ payload
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ # Ashby::InterviewSchedules provides access to Interview Schedule-related functionality in the Ashby API.
5
+ #
6
+ # Includes support for:
7
+ # - Fetching all interviews (with pagination)
8
+ #
9
+ # Example:
10
+ # Ashby::InterviewSchedules.all
11
+ #
12
+ class Interviews < Client
13
+ # Fetches all interview schedules with pagination support
14
+ def self.schedule_all(payload = {})
15
+ paginated_post('interviewSchedule.list', payload)
16
+ end
17
+
18
+ def self.plans_all
19
+ response = post('interviewPlan.list')
20
+ response['results']
21
+ end
22
+
23
+ def self.stages_all
24
+ response = post('interviewStage.list')
25
+ response['results']
26
+ end
27
+
28
+ def self.interview_by_id(id: nil)
29
+ raise ArgumentError, 'Interview ID is required' if id.to_s.strip.empty?
30
+
31
+ payload = { id: id }
32
+ response = post('interview.info', payload)
33
+ response['results']
34
+ end
35
+
36
+ def self.stage_by_id(id: nil)
37
+ raise ArgumentError, 'Stage ID is required' if id.to_s.strip.empty?
38
+
39
+ payload = { interviewStageId: id }
40
+ response = post('interviewStage.info', payload)
41
+ response['results']
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ class JobBoards < Client
5
+ def self.all
6
+ response = post('jobBoard.list')
7
+ response['results']
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ # Ashby::JobPostings provides access to job posting-related functionality in the Ashby API.
5
+ #
6
+ # Includes support for:
7
+ # - Fetching all job postings (active only by default, can include inactive)
8
+ # - Looking up a job posting by its Ashby ID
9
+ #
10
+ # Example usage:
11
+ # Ashby::JobPostings.all
12
+ # Ashby::JobPostings.all(active_only: false)
13
+ # Ashby::JobPostings.find_by_id(id: 'jp_abc456')
14
+ #
15
+ class JobPostings < Client
16
+ # Fetches all job postings
17
+ def self.all(active_only: true, job_board_id: nil)
18
+ payload = { listedOnly: active_only }
19
+ payload[:jobBoardId] = job_board_id if job_board_id
20
+
21
+ response = post('jobPosting.list', payload)
22
+ response['results']
23
+ end
24
+
25
+ # Finds a Job Posting by the Ashby ID
26
+ def self.find_by_id(id: nil)
27
+ raise ArgumentError, 'Job Posting ID is required' if id.to_s.strip.empty?
28
+
29
+ payload = { jobPostingId: id }
30
+ response = post('jobPosting.info', payload)
31
+ response['results']
32
+ end
33
+
34
+ # Finds all Job Postings associated with a given Job ID
35
+ def self.by_job_id(job_id: nil)
36
+ raise ArgumentError, 'Job ID is required' if job_id.to_s.strip.empty?
37
+
38
+ payload = { id: job_id }
39
+ response = post('job.info', payload)
40
+ job_posting_ids = response.dig('results', 'jobPostingIds') || []
41
+
42
+ job_posting_ids.map { |jp_id| find_by_id(id: jp_id) }
43
+ end
44
+ end
45
+ end
data/lib/ashby/jobs.rb ADDED
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ # Ashby::Jobs handles interactions with job-related endpoints in the Ashby API.
5
+ #
6
+ # Supports fetching all jobs with pagination, finding a job by ID, and searching
7
+ # jobs based on requisition ID or title.
8
+ #
9
+ # Example:
10
+ # Ashby::Jobs.all
11
+ # Ashby::Jobs.find('job_abc123')
12
+ # Ashby::Jobs.search(req_id: 'REQ-001')
13
+ #
14
+ class Jobs < Client
15
+ # Fetches all jobs with pagination support
16
+ def self.all(payload = {})
17
+ paginated_post('job.list', payload)
18
+ end
19
+
20
+ # Finds a job by its Ashby ID
21
+ def self.find(id, expand: '')
22
+ raise ArgumentError, 'Job ID is required' if id.to_s.strip.empty?
23
+
24
+ payload = {
25
+ id: id,
26
+ expand: expand.strip.empty? ? [] : [expand]
27
+ }
28
+ post('job.info', payload)['results']
29
+ end
30
+
31
+ # Searches for jobs based on requisition ID or title
32
+ def self.search(req_id: nil, title: nil)
33
+ payload = {}
34
+ payload[:requisitionId] = req_id.to_s if req_id
35
+ payload[:title] = title if title
36
+
37
+ raise ArgumentError, 'You must provide at least a job title or a requisition id' if payload.empty?
38
+
39
+ post('job.search', payload)['results']
40
+ end
41
+
42
+ # Fetches all job templates
43
+ def self.templates
44
+ paginated_post('jobTemplate.list')
45
+ end
46
+
47
+ def self.create(payload)
48
+ required_fields = %i[title teamId locationId]
49
+ missing = required_fields.select { |f| payload[f].to_s.strip.empty? }
50
+ raise ArgumentError, "Missing required fields: #{missing.join(', ')}" if missing.any?
51
+
52
+ post('job.create', payload)['results']
53
+ end
54
+
55
+ def self.update(id: nil, payload: {})
56
+ raise ArgumentError, 'You must provide a job id' if id.nil?
57
+
58
+ payload[:jobId] = id
59
+ post('job.update', payload)['results']
60
+ end
61
+
62
+ def self.set_status(job_id:, status:)
63
+ valid_statuses = %w[Draft Open Closed Archived]
64
+ raise ArgumentError, 'Job ID is required' if job_id.to_s.strip.empty?
65
+ raise ArgumentError, "Invalid status: #{status}" unless valid_statuses.include?(status)
66
+
67
+ payload = {
68
+ jobId: job_id,
69
+ status: status
70
+ }
71
+
72
+ post('job.setStatus', payload)['results']
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ # Ashby::Offers provides access to offer-related endpoints in the Ashby API.
5
+ #
6
+ # Supports fetching all offers with pagination and retrieving a specific offer by ID.
7
+ #
8
+ # Example:
9
+ # Ashby::Offers.all
10
+ # Ashby::Offers.find(id: 'offer_xyz789')
11
+ #
12
+ class Offers < Client
13
+ # Fetches all offers with pagination support
14
+ def self.all
15
+ paginated_post('offer.list')
16
+ end
17
+
18
+ # Finds an offer by its Ashby ID
19
+ def self.find(id: nil)
20
+ raise ArgumentError, 'Offer ID is required' if id.to_s.strip.empty?
21
+
22
+ payload = { id: id }
23
+ response = post('offer.info', payload)
24
+ response['results']
25
+ end
26
+
27
+ def self.offers_by_application_id(application_id: nil)
28
+ raise ArgumentError, 'Application ID is required' if application_id.to_s.strip.empty?
29
+
30
+ payload = { applicationId: application_id }
31
+ response = post('offer.list', payload)
32
+ response['results']
33
+ end
34
+
35
+ def self.most_recent_offer_by_application_id(application_id: nil)
36
+ raise ArgumentError, 'Application ID is required' if application_id.to_s.strip.empty?
37
+
38
+ payload = { applicationId: application_id }
39
+ response = post('offer.list', payload)
40
+ response['results'].max_by { |o| o['latestVersion']['createdAt'] }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ # Ashby::Candidates provides access to candidate-related functionality in the Ashby API.
5
+ #
6
+ # Includes support for:
7
+ # - Fetching all candidates (with pagination)
8
+ # - Searching by email or name
9
+ # - Looking up by ID
10
+ # - Adding notes to candidate profiles in plain text or HTML
11
+ #
12
+ # Example:
13
+ # Ashby::Candidates.all
14
+ # Ashby::Candidates.find(email: 'jane@company.com')
15
+ # Ashby::Candidates.find_by_id(id: 'cand_abc123')
16
+ #
17
+ # client = Ashby::Candidates.new
18
+ # client.create_candidate_note('cand_abc123', 'Reached out via email.')
19
+ # client.create_formatted_candidate_note('cand_abc123', title: 'Initial Contact',
20
+ # content: 'Spoke with Jane over Zoom.')
21
+ #
22
+ class Openings < Client
23
+ # Fetches all openings with pagination support
24
+ def self.all
25
+ paginated_post('opening.list')
26
+ end
27
+
28
+ # Finds an opening by email or name
29
+ def self.find(email: nil, name: nil)
30
+ payload = {}
31
+ payload[:email] = email if email
32
+ payload[:name] = name if name
33
+
34
+ raise ArgumentError, 'You must provide at least an email or a name' if payload.empty?
35
+
36
+ response = post('opening.search', payload)
37
+ response['results']
38
+ end
39
+
40
+ # Searches for openings by the identifier
41
+ def self.search(identifier: nil)
42
+ raise ArgumentError, 'You must provide an identifier' if identifier.to_s.strip.empty?
43
+
44
+ payload = { identifier: identifier.to_s }
45
+ response = post('opening.search', payload)
46
+ response['results']
47
+ end
48
+
49
+ # Finds an opening by its Ashby ID
50
+ def self.find_by_id(id: nil)
51
+ raise ArgumentError, 'Opening ID is required' if id.to_s.strip.empty?
52
+
53
+ payload = { openingId: id }
54
+ response = post('opening.info', payload)
55
+ response['results']
56
+ end
57
+
58
+ def self.set_status(id, state)
59
+ payload = {}
60
+ payload[:openingId] = id if id
61
+ payload[:openingState] = state if state
62
+
63
+ raise ArgumentError, 'You must provide the opening ID and the state' if payload.empty?
64
+
65
+ response = post('opening.setOpeningState', payload)
66
+ response['results']
67
+ end
68
+
69
+ def self.create(payload)
70
+ raise ArgumentError, 'No payload provided' if payload.empty?
71
+
72
+ response = post('opening.create', payload)
73
+ response['results']
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ # Ashby::Postings provides access to job posting related functionality in the Ashby API.
5
+ #
6
+ # It wraps the `jobPosting.info` and `jobPosting.list` endpoints and exposes a few
7
+ # convenience helpers for commonly accessed nested fields such as `linkedData`
8
+ # (for SEO rich results) and `applicationFormDefinition`.
9
+ #
10
+ # Endpoints referenced:
11
+ # - jobPosting.info -> Retrieve a single job posting by ID
12
+ # - jobPosting.list -> List job postings (optionally only those that are publicly listed)
13
+ # - jobPosting.update -> Update an existing job posting
14
+ #
15
+ # Example usage:
16
+ # Ashby::Postings.list # list all postings (listed + unlisted)
17
+ # Ashby::Postings.list(listed_only: true) # only postings publicly listed
18
+ # Ashby::Postings.all # (deprecated) alias to .list
19
+ # Ashby::Postings.find('jp_abc123') # posting hash
20
+ # Ashby::Postings.linked_data('jp_abc123') # structured data for SEO
21
+ # Ashby::Postings.application_form_definition('jp_abc123')
22
+ # Ashby::Postings.update(id: 'jp_abc123', payload: { title: 'Updated Title' })
23
+ #
24
+ # Note: A `Ashby::JobPostings` class already exists; this class provides a
25
+ # cleaner name (`Postings`) and a couple of ergonomic helpers while delegating
26
+ # to the same underlying API endpoints. Either class can be used interchangeably.
27
+ #
28
+ class Postings < Client
29
+ # Lists job postings. By default returns both listed and unlisted job postings.
30
+ # Pass listed_only: true to restrict to those safe for public display.
31
+ # @param listed_only [Boolean, nil] When true only include publicly listed postings; when nil omit filter.
32
+ def self.list(listed_only: nil)
33
+ payload = {}
34
+ payload[:listedOnly] = true if listed_only == true
35
+ post('jobPosting.list', payload)['results']
36
+ end
37
+
38
+ # Deprecated: Use .list(listed_only: ...) instead. Retained for backward compatibility.
39
+ def self.all(listed_only: nil, **_deprecated)
40
+ Kernel.warn('[DEPRECATION] Ashby::Postings.all is deprecated. Use Ashby::Postings.list(listed_only: ...) instead.')
41
+ list(listed_only: listed_only)
42
+ end
43
+
44
+ # Retrieve a single job posting by its Ashby Job Posting ID.
45
+ # @param id [String] The Ashby Job Posting ID (e.g., "jp_abc123")
46
+ # @return [Hash] The job posting object
47
+ def self.find(id)
48
+ raise ArgumentError, 'Job Posting ID is required' if id.to_s.strip.empty?
49
+
50
+ payload = { jobPostingId: id }
51
+ post('jobPosting.info', payload)['results']
52
+ end
53
+ class << self
54
+ alias find_by_id find
55
+ alias info find
56
+ end
57
+
58
+ # Convenience: Returns the `linkedData` section (for SEO rich results) of a posting.
59
+ def self.linked_data(id)
60
+ find(id)['linkedData']
61
+ end
62
+
63
+ # Convenience: Returns the `applicationFormDefinition` of a posting.
64
+ def self.application_form_definition(id)
65
+ find(id)['applicationFormDefinition']
66
+ end
67
+
68
+ # Update a job posting via jobPosting.update.
69
+ # You must supply at least one field to update in the payload.
70
+ #
71
+ # @param id [String] The Ashby Job Posting ID
72
+ # @param payload [Hash] Fields to update (see Ashby API docs for allowed keys)
73
+ # @return [Hash] Updated job posting object
74
+ def self.update(id:, payload: {})
75
+ raise ArgumentError, 'Job Posting ID is required' if id.to_s.strip.empty?
76
+ raise ArgumentError, 'Update payload cannot be empty' if payload.empty?
77
+
78
+ body = payload.dup
79
+ body[:jobPostingId] = id
80
+ post('jobPosting.update', body)['results']
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ # Ashby::Users provides access to user-related functionality in the Ashby API.
5
+ #
6
+ # Includes support for:
7
+ # - Fetching all users with pagination
8
+ # - Searching users by email
9
+ # - Looking up users by their Ashby ID
10
+ #
11
+ # Example usage:
12
+ # Ashby::Users.all
13
+ # Ashby::Users.find_by_email(email: 'recruiter@company.com')
14
+ # Ashby::Users.find_by_id(id: 'user_xyz789')
15
+ #
16
+ class Users < Client
17
+ # Fetches all users with pagination support
18
+ def self.all
19
+ paginated_post('user.list')
20
+ end
21
+
22
+ # Find a user by their email
23
+ def self.find_by_email(email: nil)
24
+ payload = {}
25
+ payload[:email] = email if email
26
+
27
+ raise ArgumentError, 'You must provide an email' if payload.empty?
28
+
29
+ response = post('user.search', payload)
30
+ response['results']
31
+ end
32
+
33
+ # Finds a user by their Ashby ID
34
+ def self.find_by_id(id: nil)
35
+ raise ArgumentError, 'User ID is required' if id.to_s.strip.empty?
36
+
37
+ payload = { userId: id }
38
+ response = post('user.info', payload)
39
+ response['results']
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ashby
4
+ VERSION = '0.1.3'
5
+ end
data/lib/ashby.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ashby/version'
4
+ require_relative 'ashby/configuration'
5
+ require_relative 'ashby/client'
6
+ require_relative 'ashby/candidates'
7
+ require_relative 'ashby/departments'
8
+ require_relative 'ashby/jobs'
9
+ require_relative 'ashby/offers'
10
+ require_relative 'ashby/applications'
11
+ require_relative 'ashby/openings'
12
+ require_relative 'ashby/users'
13
+ require_relative 'ashby/job_postings'
14
+ require_relative 'ashby/hiring_teams'
15
+ require_relative 'ashby/error'
16
+ require_relative 'ashby/close_reasons'
17
+ require_relative 'ashby/interviews'
18
+ require_relative 'ashby/custom_fields'
19
+ require_relative 'ashby/feedback'
20
+ require_relative 'ashby/postings'
21
+ require_relative 'ashby/job_boards'
22
+
23
+ # This module handles integration with Ashby's API
24
+ # for recruitment and hiring processes
25
+ module Ashby
26
+ extend Configuration
27
+
28
+ class Error < StandardError; end
29
+ end
data/sig/.DS_Store ADDED
Binary file
data/sig/ashby.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Ashby
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end