attio-ruby 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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +164 -0
- data/.simplecov +17 -0
- data/.yardopts +9 -0
- data/CHANGELOG.md +27 -0
- data/CONTRIBUTING.md +333 -0
- data/INTEGRATION_TEST_STATUS.md +149 -0
- data/LICENSE +21 -0
- data/README.md +638 -0
- data/Rakefile +8 -0
- data/attio-ruby.gemspec +61 -0
- data/docs/CODECOV_SETUP.md +34 -0
- data/examples/basic_usage.rb +149 -0
- data/examples/oauth_flow.rb +843 -0
- data/examples/oauth_flow_README.md +84 -0
- data/examples/typed_records_example.rb +167 -0
- data/examples/webhook_server.rb +463 -0
- data/lib/attio/api_resource.rb +539 -0
- data/lib/attio/builders/name_builder.rb +181 -0
- data/lib/attio/client.rb +160 -0
- data/lib/attio/errors.rb +126 -0
- data/lib/attio/internal/record.rb +359 -0
- data/lib/attio/oauth/client.rb +219 -0
- data/lib/attio/oauth/scope_validator.rb +162 -0
- data/lib/attio/oauth/token.rb +158 -0
- data/lib/attio/resources/attribute.rb +332 -0
- data/lib/attio/resources/comment.rb +114 -0
- data/lib/attio/resources/company.rb +224 -0
- data/lib/attio/resources/entry.rb +208 -0
- data/lib/attio/resources/list.rb +196 -0
- data/lib/attio/resources/meta.rb +113 -0
- data/lib/attio/resources/note.rb +213 -0
- data/lib/attio/resources/object.rb +66 -0
- data/lib/attio/resources/person.rb +294 -0
- data/lib/attio/resources/task.rb +147 -0
- data/lib/attio/resources/thread.rb +99 -0
- data/lib/attio/resources/typed_record.rb +98 -0
- data/lib/attio/resources/webhook.rb +224 -0
- data/lib/attio/resources/workspace_member.rb +136 -0
- data/lib/attio/util/configuration.rb +166 -0
- data/lib/attio/util/id_extractor.rb +115 -0
- data/lib/attio/util/webhook_signature.rb +175 -0
- data/lib/attio/version.rb +6 -0
- data/lib/attio/webhook/event.rb +114 -0
- data/lib/attio/webhook/signature_verifier.rb +73 -0
- data/lib/attio.rb +123 -0
- metadata +402 -0
@@ -0,0 +1,213 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../api_resource"
|
4
|
+
|
5
|
+
module Attio
|
6
|
+
# Represents a note attached to a record in Attio
|
7
|
+
class Note < APIResource
|
8
|
+
api_operations :list, :retrieve, :create, :delete
|
9
|
+
|
10
|
+
# API endpoint path for notes
|
11
|
+
# @return [String] The API path
|
12
|
+
def self.resource_path
|
13
|
+
"notes"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Read-only attributes - notes are immutable
|
17
|
+
attr_reader :parent_object, :parent_record_id, :title, :format,
|
18
|
+
:created_by_actor, :content_plaintext, :content_markdown, :tags, :metadata
|
19
|
+
|
20
|
+
# Alias for compatibility
|
21
|
+
alias_method :created_by, :created_by_actor
|
22
|
+
|
23
|
+
# Convenience method to get content based on format
|
24
|
+
def content
|
25
|
+
case format
|
26
|
+
when "plaintext"
|
27
|
+
content_plaintext
|
28
|
+
when "html", "markdown"
|
29
|
+
content_markdown
|
30
|
+
else
|
31
|
+
content_plaintext
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(attributes = {}, opts = {})
|
36
|
+
super
|
37
|
+
normalized_attrs = normalize_attributes(attributes)
|
38
|
+
@parent_object = normalized_attrs[:parent_object]
|
39
|
+
@parent_record_id = normalized_attrs[:parent_record_id]
|
40
|
+
@title = normalized_attrs[:title]
|
41
|
+
@content_plaintext = normalized_attrs[:content_plaintext]
|
42
|
+
@content_markdown = normalized_attrs[:content_markdown]
|
43
|
+
@tags = normalized_attrs[:tags] || []
|
44
|
+
@metadata = normalized_attrs[:metadata] || {}
|
45
|
+
@format = normalized_attrs[:format] || "plaintext"
|
46
|
+
@created_by_actor = normalized_attrs[:created_by_actor]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get the parent record
|
50
|
+
def parent_record(**)
|
51
|
+
return nil unless parent_object && parent_record_id
|
52
|
+
|
53
|
+
Internal::Record.retrieve(
|
54
|
+
object: parent_object,
|
55
|
+
record_id: parent_record_id,
|
56
|
+
**
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Check if note is in HTML format
|
61
|
+
def html?
|
62
|
+
format == "html"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Check if note is in plaintext format
|
66
|
+
def plaintext?
|
67
|
+
format == "plaintext"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get plaintext version of content
|
71
|
+
def to_plaintext
|
72
|
+
return content_plaintext if content_plaintext
|
73
|
+
|
74
|
+
# If no plaintext, try to get markdown/html content and strip HTML
|
75
|
+
html_content = content_markdown || content
|
76
|
+
return nil unless html_content
|
77
|
+
|
78
|
+
strip_html(html_content)
|
79
|
+
end
|
80
|
+
|
81
|
+
def resource_path
|
82
|
+
raise InvalidRequestError, "Cannot generate path without an ID" unless persisted?
|
83
|
+
note_id = id.is_a?(Hash) ? (id[:note_id] || id["note_id"]) : id
|
84
|
+
"#{self.class.resource_path}/#{note_id}"
|
85
|
+
end
|
86
|
+
|
87
|
+
# Override destroy to handle nested ID
|
88
|
+
def destroy(**opts)
|
89
|
+
raise InvalidRequestError, "Cannot destroy a note without an ID" unless persisted?
|
90
|
+
|
91
|
+
note_id = id.is_a?(Hash) ? (id[:note_id] || id["note_id"]) : id
|
92
|
+
self.class.delete(note_id, **opts)
|
93
|
+
freeze
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
# Notes cannot be updated
|
98
|
+
def save(*)
|
99
|
+
raise NotImplementedError, "Notes cannot be updated. Create a new note instead."
|
100
|
+
end
|
101
|
+
|
102
|
+
def update(*)
|
103
|
+
raise NotImplementedError, "Notes cannot be updated. Create a new note instead."
|
104
|
+
end
|
105
|
+
|
106
|
+
# Convert note to hash representation
|
107
|
+
# @return [Hash] Note data as a hash
|
108
|
+
def to_h
|
109
|
+
super.merge(
|
110
|
+
parent_object: parent_object,
|
111
|
+
parent_record_id: parent_record_id,
|
112
|
+
content: content,
|
113
|
+
format: format,
|
114
|
+
created_by_actor: created_by_actor,
|
115
|
+
content_plaintext: content_plaintext
|
116
|
+
).compact
|
117
|
+
end
|
118
|
+
|
119
|
+
class << self
|
120
|
+
# Override retrieve to handle nested ID
|
121
|
+
def retrieve(id, **opts)
|
122
|
+
note_id = id.is_a?(Hash) ? (id[:note_id] || id["note_id"]) : id
|
123
|
+
validate_id!(note_id)
|
124
|
+
response = execute_request(:GET, "#{resource_path}/#{note_id}", {}, opts)
|
125
|
+
new(response["data"] || response, opts)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Override create to handle validation and parameter mapping
|
129
|
+
def create(**kwargs)
|
130
|
+
# Extract options from kwargs
|
131
|
+
opts = {}
|
132
|
+
opts[:api_key] = kwargs.delete(:api_key) if kwargs.key?(:api_key)
|
133
|
+
|
134
|
+
# Map object/record_id to parent_object/parent_record_id
|
135
|
+
normalized_params = {
|
136
|
+
parent_object: kwargs[:object] || kwargs[:parent_object],
|
137
|
+
parent_record_id: kwargs[:record_id] || kwargs[:parent_record_id],
|
138
|
+
title: kwargs[:title] || kwargs[:content] || "Note",
|
139
|
+
content: kwargs[:content],
|
140
|
+
format: kwargs[:format]
|
141
|
+
}
|
142
|
+
|
143
|
+
prepared_params = prepare_params_for_create(normalized_params)
|
144
|
+
response = execute_request(:POST, resource_path, prepared_params, opts)
|
145
|
+
new(response["data"] || response, opts)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Override create to handle validation
|
149
|
+
def prepare_params_for_create(params)
|
150
|
+
validate_parent!(params[:parent_object], params[:parent_record_id])
|
151
|
+
validate_content!(params[:content])
|
152
|
+
validate_format!(params[:format]) if params[:format]
|
153
|
+
|
154
|
+
{
|
155
|
+
data: {
|
156
|
+
title: params[:title],
|
157
|
+
parent_object: params[:parent_object],
|
158
|
+
parent_record_id: params[:parent_record_id],
|
159
|
+
content: params[:content],
|
160
|
+
format: params[:format] || "plaintext"
|
161
|
+
}
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
# Get notes for a record
|
166
|
+
def for_record(params = {}, object:, record_id:, **)
|
167
|
+
list(
|
168
|
+
params.merge(
|
169
|
+
parent_object: object,
|
170
|
+
parent_record_id: record_id
|
171
|
+
),
|
172
|
+
**
|
173
|
+
)
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def validate_parent!(parent_object, parent_record_id)
|
179
|
+
if parent_object.nil? || parent_object.to_s.empty?
|
180
|
+
raise ArgumentError, "parent_object is required"
|
181
|
+
end
|
182
|
+
|
183
|
+
if parent_record_id.nil? || parent_record_id.to_s.empty?
|
184
|
+
raise ArgumentError, "parent_record_id is required"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def validate_content!(content)
|
189
|
+
if content.nil? || content.to_s.strip.empty?
|
190
|
+
raise ArgumentError, "content cannot be empty"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def validate_format!(format)
|
195
|
+
valid_formats = %w[plaintext html]
|
196
|
+
unless valid_formats.include?(format.to_s)
|
197
|
+
raise ArgumentError, "Invalid format: #{format}. Valid formats: #{valid_formats.join(", ")}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def strip_html(html)
|
205
|
+
return html unless html.is_a?(String)
|
206
|
+
|
207
|
+
# Basic HTML stripping (production apps should use a proper HTML parser)
|
208
|
+
html.gsub(/<[^>]+>/, " ")
|
209
|
+
.gsub(/\s+/, " ")
|
210
|
+
.strip
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../api_resource"
|
4
|
+
|
5
|
+
module Attio
|
6
|
+
# Represents an object type in Attio (e.g., People, Companies)
|
7
|
+
class Object < APIResource
|
8
|
+
api_operations :list, :retrieve, :create, :update, :delete
|
9
|
+
|
10
|
+
# API endpoint path for objects
|
11
|
+
# @return [String] The API path
|
12
|
+
def self.resource_path
|
13
|
+
"objects"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Define known attributes
|
17
|
+
attr_reader :api_slug, :singular_noun, :plural_noun, :created_by_actor
|
18
|
+
|
19
|
+
def initialize(attributes = {}, opts = {})
|
20
|
+
super
|
21
|
+
normalized_attrs = normalize_attributes(attributes)
|
22
|
+
@api_slug = normalized_attrs[:api_slug]
|
23
|
+
@singular_noun = normalized_attrs[:singular_noun]
|
24
|
+
@plural_noun = normalized_attrs[:plural_noun]
|
25
|
+
@created_by_actor = normalized_attrs[:created_by_actor]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get all attributes for this object
|
29
|
+
def attributes(**)
|
30
|
+
Attribute.list(parent_object: api_slug || id, **)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Create a new attribute for this object
|
34
|
+
def create_attribute(params = {}, **)
|
35
|
+
Attribute.create(params.merge(parent_object: api_slug || id), **)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get records for this object
|
39
|
+
def records(params = {}, **)
|
40
|
+
Internal::Record.list(object: api_slug || id, **params, **)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Create a record for this object
|
44
|
+
def create_record(values = {}, **)
|
45
|
+
Internal::Record.create(object: api_slug || id, values: values, **)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Find by API slug
|
49
|
+
def self.find_by_slug(slug, **opts)
|
50
|
+
retrieve(slug, **opts)
|
51
|
+
rescue NotFoundError
|
52
|
+
list(**opts).find { |obj| obj.api_slug == slug }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get standard objects
|
56
|
+
def self.people(**)
|
57
|
+
find_by_slug("people", **)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get the standard Companies object
|
61
|
+
# @return [Object] The companies object
|
62
|
+
def self.companies(**)
|
63
|
+
find_by_slug("companies", **)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,294 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "typed_record"
|
4
|
+
|
5
|
+
module Attio
|
6
|
+
# Represents a person record in Attio
|
7
|
+
# Provides convenient methods for working with people and their attributes
|
8
|
+
class Person < TypedRecord
|
9
|
+
object_type "people"
|
10
|
+
|
11
|
+
# Set the person's name using a more intuitive interface
|
12
|
+
# @param first [String] First name
|
13
|
+
# @param last [String] Last name
|
14
|
+
# @param middle [String] Middle name (optional)
|
15
|
+
# @param full [String] Full name (optional, will be generated if not provided)
|
16
|
+
def set_name(first: nil, last: nil, middle: nil, full: nil)
|
17
|
+
name_data = {}
|
18
|
+
name_data[:first_name] = first if first
|
19
|
+
name_data[:last_name] = last if last
|
20
|
+
name_data[:middle_name] = middle if middle
|
21
|
+
|
22
|
+
# Generate full name if not provided
|
23
|
+
if full
|
24
|
+
name_data[:full_name] = full
|
25
|
+
elsif first || last
|
26
|
+
parts = [first, middle, last].compact
|
27
|
+
name_data[:full_name] = parts.join(" ") unless parts.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Attio expects name as an array with a single hash
|
31
|
+
self[:name] = [name_data] unless name_data.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set the person's name using a hash or string
|
35
|
+
# @param name_value [Hash, String] Either a hash with first/last/middle/full keys or a full name string
|
36
|
+
def name=(name_value)
|
37
|
+
case name_value
|
38
|
+
when Hash
|
39
|
+
set_name(**name_value)
|
40
|
+
when String
|
41
|
+
set_name(full: name_value)
|
42
|
+
else
|
43
|
+
raise ArgumentError, "Name must be a Hash or String"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get the person's full name
|
48
|
+
# @return [String, nil] The full name or nil if not set
|
49
|
+
def full_name
|
50
|
+
extract_name_field("full_name")
|
51
|
+
end
|
52
|
+
|
53
|
+
# Get the person's first name
|
54
|
+
# @return [String, nil] The first name or nil if not set
|
55
|
+
def first_name
|
56
|
+
extract_name_field("first_name")
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get the person's last name
|
60
|
+
# @return [String, nil] The last name or nil if not set
|
61
|
+
def last_name
|
62
|
+
extract_name_field("last_name")
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Extract a field from the name data structure
|
68
|
+
# @param field [String] The field to extract
|
69
|
+
# @return [String, nil] The field value or nil
|
70
|
+
def extract_name_field(field)
|
71
|
+
name_value = self[:name]
|
72
|
+
return nil unless name_value
|
73
|
+
|
74
|
+
name_hash = normalize_to_hash(name_value)
|
75
|
+
name_hash[field] || name_hash[field.to_sym]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Extract primary value from various data structures
|
79
|
+
# @param value [Array, Hash, Object] The value to extract from
|
80
|
+
# @param field [String] The field name for hash extraction
|
81
|
+
# @return [String, nil] The extracted value
|
82
|
+
def extract_primary_value(value, field)
|
83
|
+
case value
|
84
|
+
when Array
|
85
|
+
return nil if value.empty?
|
86
|
+
first_item = value.first
|
87
|
+
if first_item.is_a?(Hash)
|
88
|
+
first_item[field] || first_item[field.to_sym]
|
89
|
+
else
|
90
|
+
first_item.to_s
|
91
|
+
end
|
92
|
+
when Hash
|
93
|
+
value[field] || value[field.to_sym]
|
94
|
+
else
|
95
|
+
value.to_s
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Normalize various name formats to a hash
|
100
|
+
# @param value [Array, Hash] The value to normalize
|
101
|
+
# @return [Hash] The normalized hash
|
102
|
+
def normalize_to_hash(value)
|
103
|
+
case value
|
104
|
+
when Array
|
105
|
+
value.first.is_a?(Hash) ? value.first : {}
|
106
|
+
when Hash
|
107
|
+
value
|
108
|
+
else
|
109
|
+
{}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
public
|
114
|
+
|
115
|
+
# Add an email address
|
116
|
+
# @param email [String] The email address to add
|
117
|
+
def add_email(email)
|
118
|
+
emails = self[:email_addresses] || []
|
119
|
+
# Ensure it's an array
|
120
|
+
emails = [emails] unless emails.is_a?(Array)
|
121
|
+
|
122
|
+
# Add the email if it's not already present
|
123
|
+
emails << email unless emails.include?(email)
|
124
|
+
self[:email_addresses] = emails
|
125
|
+
end
|
126
|
+
|
127
|
+
# Get the primary email address
|
128
|
+
# @return [String, nil] The primary email or nil if not set
|
129
|
+
def email
|
130
|
+
emails = self[:email_addresses]
|
131
|
+
return nil unless emails
|
132
|
+
|
133
|
+
extract_primary_value(emails, "email_address")
|
134
|
+
end
|
135
|
+
|
136
|
+
# Add a phone number
|
137
|
+
# @param number [String] The phone number
|
138
|
+
# @param country_code [String] The country code (e.g., "US")
|
139
|
+
def add_phone(number, country_code: "US")
|
140
|
+
phones = self[:phone_numbers] || []
|
141
|
+
phones = [phones] unless phones.is_a?(Array)
|
142
|
+
|
143
|
+
phone_data = {
|
144
|
+
original_phone_number: number,
|
145
|
+
country_code: country_code
|
146
|
+
}
|
147
|
+
|
148
|
+
phones << phone_data
|
149
|
+
self[:phone_numbers] = phones
|
150
|
+
end
|
151
|
+
|
152
|
+
# Get the primary phone number
|
153
|
+
# @return [String, nil] The primary phone number or nil if not set
|
154
|
+
def phone
|
155
|
+
phones = self[:phone_numbers]
|
156
|
+
return nil unless phones
|
157
|
+
|
158
|
+
extract_primary_value(phones, "original_phone_number")
|
159
|
+
end
|
160
|
+
|
161
|
+
# Set the job title
|
162
|
+
# @param title [String] The job title
|
163
|
+
def job_title=(title)
|
164
|
+
self[:job_title] = title
|
165
|
+
end
|
166
|
+
|
167
|
+
# Associate with a company
|
168
|
+
# @param company [Company, String] A Company instance or company ID
|
169
|
+
def company=(company)
|
170
|
+
if company.is_a?(Company)
|
171
|
+
# Extract ID properly from company instance
|
172
|
+
company_id = company.id.is_a?(Hash) ? company.id["record_id"] : company.id
|
173
|
+
self[:company] = [{
|
174
|
+
target_object: "companies",
|
175
|
+
target_record_id: company_id
|
176
|
+
}]
|
177
|
+
elsif company.is_a?(String)
|
178
|
+
self[:company] = [{
|
179
|
+
target_object: "companies",
|
180
|
+
target_record_id: company
|
181
|
+
}]
|
182
|
+
elsif company.nil?
|
183
|
+
self[:company] = nil
|
184
|
+
else
|
185
|
+
raise ArgumentError, "Company must be a Company instance or ID string"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class << self
|
190
|
+
# Create a person with a simplified interface
|
191
|
+
# @param attributes [Hash] Person attributes
|
192
|
+
# @option attributes [String] :first_name First name
|
193
|
+
# @option attributes [String] :last_name Last name
|
194
|
+
# @option attributes [String] :email Email address
|
195
|
+
# @option attributes [String] :phone Phone number
|
196
|
+
# @option attributes [String] :job_title Job title
|
197
|
+
# @option attributes [Hash] :values Raw values hash (for advanced use)
|
198
|
+
def create(first_name: nil, last_name: nil, full_name: nil, email: nil, phone: nil,
|
199
|
+
job_title: nil, company: nil, values: {}, **opts)
|
200
|
+
# Build the values hash
|
201
|
+
values[:name] ||= []
|
202
|
+
if first_name || last_name || full_name
|
203
|
+
name_data = {}
|
204
|
+
|
205
|
+
# If only full_name is provided, try to parse it
|
206
|
+
if full_name && !first_name && !last_name
|
207
|
+
parts = full_name.split(" ")
|
208
|
+
if parts.length >= 2
|
209
|
+
name_data[:first_name] = parts.first
|
210
|
+
name_data[:last_name] = parts[1..].join(" ")
|
211
|
+
else
|
212
|
+
name_data[:first_name] = full_name
|
213
|
+
end
|
214
|
+
name_data[:full_name] = full_name
|
215
|
+
else
|
216
|
+
name_data[:first_name] = first_name if first_name
|
217
|
+
name_data[:last_name] = last_name if last_name
|
218
|
+
name_data[:full_name] = full_name || [first_name, last_name].compact.join(" ")
|
219
|
+
end
|
220
|
+
|
221
|
+
values[:name] = [name_data]
|
222
|
+
end
|
223
|
+
|
224
|
+
values[:email_addresses] = [email] if email && !values[:email_addresses]
|
225
|
+
|
226
|
+
if phone && !values[:phone_numbers]
|
227
|
+
values[:phone_numbers] = [{
|
228
|
+
original_phone_number: phone,
|
229
|
+
country_code: opts.delete(:country_code) || "US"
|
230
|
+
}]
|
231
|
+
end
|
232
|
+
|
233
|
+
values[:job_title] = job_title if job_title && !values[:job_title]
|
234
|
+
|
235
|
+
if company && !values[:company]
|
236
|
+
company_ref = if company.is_a?(Company)
|
237
|
+
company_id = company.id.is_a?(Hash) ? company.id["record_id"] : company.id
|
238
|
+
{
|
239
|
+
target_object: "companies",
|
240
|
+
target_record_id: company_id
|
241
|
+
}
|
242
|
+
elsif company.is_a?(String)
|
243
|
+
{
|
244
|
+
target_object: "companies",
|
245
|
+
target_record_id: company
|
246
|
+
}
|
247
|
+
end
|
248
|
+
values[:company] = [company_ref] if company_ref
|
249
|
+
end
|
250
|
+
|
251
|
+
super(values: values, **opts)
|
252
|
+
end
|
253
|
+
|
254
|
+
# Find people by email
|
255
|
+
# @param email [String] Email address to search for
|
256
|
+
def find_by_email(email, **opts)
|
257
|
+
list(**opts.merge(
|
258
|
+
filter: {
|
259
|
+
email_addresses: {
|
260
|
+
email_address: {
|
261
|
+
"$eq": email
|
262
|
+
}
|
263
|
+
}
|
264
|
+
}
|
265
|
+
)).first
|
266
|
+
end
|
267
|
+
|
268
|
+
# Search people by query
|
269
|
+
# @param query [String] Query to search for
|
270
|
+
def search(query, **opts)
|
271
|
+
# Search across name fields
|
272
|
+
list(**opts.merge(
|
273
|
+
filter: {
|
274
|
+
"$or": [
|
275
|
+
{name: {first_name: {"$contains": query}}},
|
276
|
+
{name: {last_name: {"$contains": query}}},
|
277
|
+
{name: {full_name: {"$contains": query}}}
|
278
|
+
]
|
279
|
+
}
|
280
|
+
))
|
281
|
+
end
|
282
|
+
|
283
|
+
# Find people by name
|
284
|
+
# @param name [String] Name to search for
|
285
|
+
def find_by_name(name, **opts)
|
286
|
+
results = search(name, **opts)
|
287
|
+
results.first
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Convenience alias
|
293
|
+
People = Person
|
294
|
+
end
|