attio 0.1.1 → 0.2.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 +4 -4
- data/.github/workflows/ci.yml +39 -15
- data/.github/workflows/coverage.yml +67 -0
- data/.github/workflows/pr_checks.yml +25 -7
- data/.github/workflows/release.yml +27 -13
- data/.github/workflows/tests.yml +67 -0
- data/.rubocop.yml +362 -90
- data/CHANGELOG.md +49 -1
- data/CONCEPTS.md +428 -0
- data/CONTRIBUTING.md +4 -4
- data/Gemfile +8 -5
- data/Gemfile.lock +4 -4
- data/README.md +164 -2
- data/Rakefile +8 -6
- data/attio.gemspec +6 -7
- data/danger/Dangerfile +22 -34
- data/docs/example.rb +30 -29
- data/examples/advanced_filtering.rb +178 -0
- data/examples/basic_usage.rb +110 -0
- data/examples/collaboration_example.rb +173 -0
- data/examples/full_workflow.rb +348 -0
- data/examples/notes_and_tasks.rb +200 -0
- data/lib/attio/client.rb +67 -29
- data/lib/attio/connection_pool.rb +26 -14
- data/lib/attio/errors.rb +4 -2
- data/lib/attio/http_client.rb +70 -41
- data/lib/attio/logger.rb +37 -27
- data/lib/attio/resources/attributes.rb +12 -5
- data/lib/attio/resources/base.rb +66 -27
- data/lib/attio/resources/comments.rb +147 -0
- data/lib/attio/resources/lists.rb +21 -24
- data/lib/attio/resources/notes.rb +110 -0
- data/lib/attio/resources/objects.rb +11 -4
- data/lib/attio/resources/records.rb +49 -67
- data/lib/attio/resources/tasks.rb +131 -0
- data/lib/attio/resources/threads.rb +154 -0
- data/lib/attio/resources/users.rb +10 -4
- data/lib/attio/resources/workspaces.rb +9 -1
- data/lib/attio/retry_handler.rb +19 -11
- data/lib/attio/version.rb +3 -1
- data/lib/attio.rb +15 -9
- metadata +13 -18
- data/run_tests.rb +0 -52
- data/test_basic.rb +0 -51
- data/test_typhoeus.rb +0 -31
data/lib/attio/logger.rb
CHANGED
@@ -1,7 +1,17 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "json"
|
3
5
|
|
4
6
|
module Attio
|
7
|
+
# Enhanced logger for structured logging with JSON formatting
|
8
|
+
#
|
9
|
+
# This logger extends Ruby's standard Logger to provide structured
|
10
|
+
# logging with contextual information and JSON output.
|
11
|
+
#
|
12
|
+
# @example Basic usage
|
13
|
+
# logger = Attio::Logger.new(STDOUT)
|
14
|
+
# logger.info("API request", method: "GET", path: "/users")
|
5
15
|
class Logger < ::Logger
|
6
16
|
def initialize(logdev, level: ::Logger::INFO, formatter: nil)
|
7
17
|
super(logdev)
|
@@ -25,23 +35,21 @@ module Attio
|
|
25
35
|
super(format_message(message, context))
|
26
36
|
end
|
27
37
|
|
28
|
-
private
|
29
|
-
|
30
|
-
def format_message(message, context)
|
38
|
+
private def format_message(message, context)
|
31
39
|
return message if context.empty?
|
32
|
-
|
40
|
+
|
33
41
|
{
|
34
42
|
message: message,
|
35
|
-
**context
|
43
|
+
**context,
|
36
44
|
}
|
37
45
|
end
|
38
46
|
|
39
|
-
def default_formatter
|
47
|
+
private def default_formatter
|
40
48
|
proc do |severity, datetime, progname, msg|
|
41
49
|
data = {
|
42
50
|
timestamp: datetime.iso8601,
|
43
51
|
level: severity,
|
44
|
-
progname: progname
|
52
|
+
progname: progname,
|
45
53
|
}
|
46
54
|
|
47
55
|
if msg.is_a?(Hash)
|
@@ -55,6 +63,12 @@ module Attio
|
|
55
63
|
end
|
56
64
|
end
|
57
65
|
|
66
|
+
# Specialized logger for API request/response logging
|
67
|
+
#
|
68
|
+
# This class provides sanitized logging of HTTP requests and responses,
|
69
|
+
# automatically redacting sensitive information like API keys.
|
70
|
+
#
|
71
|
+
# @api private
|
58
72
|
class RequestLogger
|
59
73
|
attr_reader :logger, :log_level
|
60
74
|
|
@@ -67,39 +81,35 @@ module Attio
|
|
67
81
|
return unless logger
|
68
82
|
|
69
83
|
logger.send(log_level, "API Request",
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
)
|
84
|
+
method: method.to_s.upcase,
|
85
|
+
url: url,
|
86
|
+
headers: sanitize_headers(headers),
|
87
|
+
body: sanitize_body(body))
|
75
88
|
end
|
76
89
|
|
77
90
|
def log_response(response, duration)
|
78
91
|
return unless logger
|
79
92
|
|
80
93
|
logger.send(log_level, "API Response",
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
)
|
94
|
+
status: response.code,
|
95
|
+
duration_ms: (duration * 1000).round(2),
|
96
|
+
headers: response.headers,
|
97
|
+
body_size: response.body&.bytesize)
|
86
98
|
end
|
87
99
|
|
88
|
-
private
|
89
|
-
|
90
|
-
def sanitize_headers(headers)
|
100
|
+
private def sanitize_headers(headers)
|
91
101
|
headers.transform_values do |value|
|
92
|
-
if value.include?(
|
93
|
-
value.gsub(/Bearer\s+[\w\-]+/,
|
102
|
+
if value.include?("Bearer")
|
103
|
+
value.gsub(/Bearer\s+[\w\-]+/, "Bearer [REDACTED]")
|
94
104
|
else
|
95
105
|
value
|
96
106
|
end
|
97
107
|
end
|
98
108
|
end
|
99
109
|
|
100
|
-
def sanitize_body(body)
|
110
|
+
private def sanitize_body(body)
|
101
111
|
return nil unless body
|
102
|
-
|
112
|
+
|
103
113
|
if body.is_a?(String) && body.length > 1000
|
104
114
|
"#{body[0..1000]}... (truncated)"
|
105
115
|
else
|
@@ -107,4 +117,4 @@ module Attio
|
|
107
117
|
end
|
108
118
|
end
|
109
119
|
end
|
110
|
-
end
|
120
|
+
end
|
@@ -1,5 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attio
|
2
4
|
module Resources
|
5
|
+
# API resource for managing Attio attributes
|
6
|
+
#
|
7
|
+
# Attributes define custom fields that can be added to objects
|
8
|
+
# and records in your Attio workspace.
|
9
|
+
#
|
10
|
+
# @example Listing attributes
|
11
|
+
# client.attributes.list(object: "people")
|
3
12
|
class Attributes < Base
|
4
13
|
def list(object:, **params)
|
5
14
|
validate_object!(object)
|
@@ -12,15 +21,13 @@ module Attio
|
|
12
21
|
request(:get, "objects/#{object}/attributes/#{id_or_slug}")
|
13
22
|
end
|
14
23
|
|
15
|
-
private
|
16
|
-
|
17
|
-
def validate_object!(object)
|
24
|
+
private def validate_object!(object)
|
18
25
|
raise ArgumentError, "Object type is required" if object.nil? || object.to_s.strip.empty?
|
19
26
|
end
|
20
27
|
|
21
|
-
def validate_id_or_slug!(id_or_slug)
|
28
|
+
private def validate_id_or_slug!(id_or_slug)
|
22
29
|
raise ArgumentError, "Attribute ID or slug is required" if id_or_slug.nil? || id_or_slug.to_s.strip.empty?
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|
26
|
-
end
|
33
|
+
end
|
data/lib/attio/resources/base.rb
CHANGED
@@ -1,55 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Base class for all API resource classes.
|
4
|
+
#
|
5
|
+
# This class provides common functionality and request handling
|
6
|
+
# for all Attio API resource implementations.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @author Ernest Sim
|
10
|
+
# @since 1.0.0
|
1
11
|
module Attio
|
2
12
|
module Resources
|
3
|
-
# Base class for all API resource classes.
|
4
|
-
#
|
5
|
-
# This class provides common functionality and request handling
|
6
|
-
# for all Attio API resource implementations.
|
7
|
-
#
|
8
|
-
# @api private
|
9
|
-
# @author Ernest Sim
|
10
|
-
# @since 1.0.0
|
11
13
|
class Base
|
12
14
|
# @return [Client] The API client instance
|
13
15
|
attr_reader :client
|
14
16
|
|
15
17
|
# Initialize a new resource instance.
|
16
|
-
#
|
18
|
+
#
|
17
19
|
# @param client [Client] The API client instance
|
18
20
|
# @raise [ArgumentError] if client is nil
|
19
21
|
def initialize(client)
|
20
22
|
raise ArgumentError, "Client is required" unless client
|
23
|
+
|
21
24
|
@client = client
|
22
25
|
end
|
23
26
|
|
24
|
-
|
27
|
+
# Common validation methods that can be used by all resource classes
|
28
|
+
|
29
|
+
# Validates that an ID parameter is present and not empty
|
30
|
+
# @param id [String] The ID to validate
|
31
|
+
# @param resource_name [String] The resource name for the error message
|
32
|
+
# @raise [ArgumentError] if id is nil or empty
|
33
|
+
private def validate_id!(id, resource_name = "Resource")
|
34
|
+
return unless id.nil? || id.to_s.strip.empty?
|
35
|
+
|
36
|
+
raise ArgumentError, "#{resource_name} ID is required"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Validates that data is not empty
|
40
|
+
# @param data [Hash] The data to validate
|
41
|
+
# @param operation [String] The operation name for the error message
|
42
|
+
# @raise [ArgumentError] if data is empty
|
43
|
+
private def validate_data!(data, operation = "Operation")
|
44
|
+
raise ArgumentError, "#{operation} data is required" if data.nil? || data.empty?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Validates that a string parameter is present and not empty
|
48
|
+
# @param value [String] The value to validate
|
49
|
+
# @param field_name [String] The field name for the error message
|
50
|
+
# @raise [ArgumentError] if value is nil or empty
|
51
|
+
private def validate_required_string!(value, field_name)
|
52
|
+
return unless value.nil? || value.to_s.strip.empty?
|
53
|
+
|
54
|
+
raise ArgumentError, "#{field_name} is required"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Validates parent object and record ID together
|
58
|
+
# @param parent_object [String] The parent object type
|
59
|
+
# @param parent_record_id [String] The parent record ID
|
60
|
+
# @raise [ArgumentError] if either is missing
|
61
|
+
private def validate_parent!(parent_object, parent_record_id)
|
62
|
+
validate_required_string!(parent_object, "Parent object")
|
63
|
+
validate_required_string!(parent_record_id, "Parent record ID")
|
64
|
+
end
|
65
|
+
|
66
|
+
private def request(method, path, params = {})
|
67
|
+
connection = client.connection
|
25
68
|
|
26
|
-
# Make an HTTP request to the API.
|
27
|
-
#
|
28
|
-
# @param method [Symbol] The HTTP method (:get, :post, :patch, :put, :delete)
|
29
|
-
# @param path [String] The API endpoint path
|
30
|
-
# @param params [Hash] Request parameters (default: {})
|
31
|
-
#
|
32
|
-
# @return [Hash] The API response
|
33
|
-
# @raise [ArgumentError] if method is unsupported
|
34
|
-
#
|
35
|
-
# @api private
|
36
|
-
def request(method, path, params = {})
|
37
69
|
case method
|
38
70
|
when :get
|
39
|
-
|
71
|
+
handle_get_request(connection, path, params)
|
40
72
|
when :post
|
41
|
-
|
73
|
+
connection.post(path, params)
|
42
74
|
when :patch
|
43
|
-
|
75
|
+
connection.patch(path, params)
|
44
76
|
when :put
|
45
|
-
|
77
|
+
connection.put(path, params)
|
46
78
|
when :delete
|
47
|
-
|
79
|
+
handle_delete_request(connection, path, params)
|
48
80
|
else
|
49
81
|
raise ArgumentError, "Unsupported HTTP method: #{method}"
|
50
82
|
end
|
51
83
|
end
|
52
84
|
|
85
|
+
private def handle_get_request(connection, path, params)
|
86
|
+
params.empty? ? connection.get(path) : connection.get(path, params)
|
87
|
+
end
|
88
|
+
|
89
|
+
private def handle_delete_request(connection, path, params)
|
90
|
+
params.empty? ? connection.delete(path) : connection.delete(path, params)
|
91
|
+
end
|
53
92
|
end
|
54
93
|
end
|
55
|
-
end
|
94
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Attio
|
4
|
+
module Resources
|
5
|
+
# API resource for managing comments in Attio
|
6
|
+
#
|
7
|
+
# Comments can be added to records and threads for collaboration.
|
8
|
+
#
|
9
|
+
# @example Creating a comment on a record
|
10
|
+
# client.comments.create(
|
11
|
+
# parent_object: "people",
|
12
|
+
# parent_record_id: "person_123",
|
13
|
+
# content: "Just had a great call with this lead!"
|
14
|
+
# )
|
15
|
+
#
|
16
|
+
# @example Creating a comment in a thread
|
17
|
+
# client.comments.create(
|
18
|
+
# thread_id: "thread_456",
|
19
|
+
# content: "Following up on our discussion..."
|
20
|
+
# )
|
21
|
+
class Comments < Base
|
22
|
+
# List comments for a parent record or thread
|
23
|
+
#
|
24
|
+
# @param params [Hash] Query parameters
|
25
|
+
# @option params [String] :parent_object Parent object type
|
26
|
+
# @option params [String] :parent_record_id Parent record ID
|
27
|
+
# @option params [String] :thread_id Thread ID
|
28
|
+
# @option params [Integer] :limit Number of comments to return
|
29
|
+
# @option params [String] :cursor Pagination cursor
|
30
|
+
#
|
31
|
+
# @return [Hash] API response containing comments
|
32
|
+
def list(**params)
|
33
|
+
validate_list_params!(params)
|
34
|
+
request(:get, "comments", params)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get a specific comment by ID
|
38
|
+
#
|
39
|
+
# @param id [String] The comment ID
|
40
|
+
#
|
41
|
+
# @return [Hash] The comment data
|
42
|
+
# @raise [ArgumentError] if id is nil or empty
|
43
|
+
def get(id:)
|
44
|
+
validate_id!(id, "Comment")
|
45
|
+
request(:get, "comments/#{id}")
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create a new comment
|
49
|
+
#
|
50
|
+
# @param content [String] The comment content (supports markdown)
|
51
|
+
# @param parent_object [String] Parent object type (required if no thread_id)
|
52
|
+
# @param parent_record_id [String] Parent record ID (required if no thread_id)
|
53
|
+
# @param thread_id [String] Thread ID (required if no parent_object/parent_record_id)
|
54
|
+
# @param data [Hash] Additional comment data
|
55
|
+
#
|
56
|
+
# @return [Hash] The created comment
|
57
|
+
# @raise [ArgumentError] if required parameters are missing
|
58
|
+
def create(content:, parent_object: nil, parent_record_id: nil, thread_id: nil, **data)
|
59
|
+
validate_required_string!(content, "Comment content")
|
60
|
+
validate_create_params!(parent_object, parent_record_id, thread_id)
|
61
|
+
|
62
|
+
params = data.merge(content: content)
|
63
|
+
|
64
|
+
if thread_id
|
65
|
+
params[:thread_id] = thread_id
|
66
|
+
else
|
67
|
+
params[:parent_object] = parent_object
|
68
|
+
params[:parent_record_id] = parent_record_id
|
69
|
+
end
|
70
|
+
|
71
|
+
request(:post, "comments", params)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Update an existing comment
|
75
|
+
#
|
76
|
+
# @param id [String] The comment ID
|
77
|
+
# @param content [String] The new content
|
78
|
+
#
|
79
|
+
# @return [Hash] The updated comment
|
80
|
+
# @raise [ArgumentError] if id or content is invalid
|
81
|
+
def update(id:, content:)
|
82
|
+
validate_id!(id, "Comment")
|
83
|
+
validate_required_string!(content, "Comment content")
|
84
|
+
request(:patch, "comments/#{id}", { content: content })
|
85
|
+
end
|
86
|
+
|
87
|
+
# Delete a comment
|
88
|
+
#
|
89
|
+
# @param id [String] The comment ID to delete
|
90
|
+
#
|
91
|
+
# @return [Hash] Deletion confirmation
|
92
|
+
# @raise [ArgumentError] if id is nil or empty
|
93
|
+
def delete(id:)
|
94
|
+
validate_id!(id, "Comment")
|
95
|
+
request(:delete, "comments/#{id}")
|
96
|
+
end
|
97
|
+
|
98
|
+
# React to a comment with an emoji
|
99
|
+
#
|
100
|
+
# @param id [String] The comment ID
|
101
|
+
# @param emoji [String] The emoji reaction (e.g., "👍", "❤️")
|
102
|
+
#
|
103
|
+
# @return [Hash] The updated comment with reaction
|
104
|
+
# @raise [ArgumentError] if id or emoji is invalid
|
105
|
+
def react(id:, emoji:)
|
106
|
+
validate_id!(id, "Comment")
|
107
|
+
validate_required_string!(emoji, "Emoji")
|
108
|
+
request(:post, "comments/#{id}/reactions", { emoji: emoji })
|
109
|
+
end
|
110
|
+
|
111
|
+
# Remove a reaction from a comment
|
112
|
+
#
|
113
|
+
# @param id [String] The comment ID
|
114
|
+
# @param emoji [String] The emoji reaction to remove
|
115
|
+
#
|
116
|
+
# @return [Hash] The updated comment
|
117
|
+
# @raise [ArgumentError] if id or emoji is invalid
|
118
|
+
def unreact(id:, emoji:)
|
119
|
+
validate_id!(id, "Comment")
|
120
|
+
validate_required_string!(emoji, "Emoji")
|
121
|
+
request(:delete, "comments/#{id}/reactions/#{CGI.escape(emoji)}")
|
122
|
+
end
|
123
|
+
|
124
|
+
private def validate_list_params!(params)
|
125
|
+
has_parent = params[:parent_object] && params[:parent_record_id]
|
126
|
+
has_thread = params[:thread_id]
|
127
|
+
|
128
|
+
return if has_parent || has_thread
|
129
|
+
|
130
|
+
raise ArgumentError, "Must provide either parent_object/parent_record_id or thread_id"
|
131
|
+
end
|
132
|
+
|
133
|
+
private def validate_create_params!(parent_object, parent_record_id, thread_id)
|
134
|
+
has_parent = parent_object && parent_record_id
|
135
|
+
has_thread = thread_id
|
136
|
+
|
137
|
+
unless has_parent || has_thread
|
138
|
+
raise ArgumentError, "Must provide either parent_object/parent_record_id or thread_id"
|
139
|
+
end
|
140
|
+
|
141
|
+
return unless has_parent && has_thread
|
142
|
+
|
143
|
+
raise ArgumentError, "Cannot provide both parent and thread parameters"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -1,56 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attio
|
2
4
|
module Resources
|
5
|
+
# API resource for managing Attio lists and list entries
|
6
|
+
#
|
7
|
+
# Lists are custom collections for organizing records in your workspace.
|
8
|
+
#
|
9
|
+
# @example Listing all lists
|
10
|
+
# client.lists.list
|
11
|
+
#
|
12
|
+
# @example Adding an entry to a list
|
13
|
+
# client.lists.create_entry(id: "list_id", data: { record_id: "rec_123" })
|
3
14
|
class Lists < Base
|
4
15
|
def list(**params)
|
5
16
|
request(:get, "lists", params)
|
6
17
|
end
|
7
18
|
|
8
19
|
def get(id:)
|
9
|
-
validate_id!(id)
|
20
|
+
validate_id!(id, "List")
|
10
21
|
request(:get, "lists/#{id}")
|
11
22
|
end
|
12
23
|
|
13
24
|
def entries(id:, **params)
|
14
|
-
validate_id!(id)
|
25
|
+
validate_id!(id, "List")
|
15
26
|
request(:get, "lists/#{id}/entries", params)
|
16
27
|
end
|
17
28
|
|
18
29
|
def create_entry(id:, data:)
|
19
|
-
validate_id!(id)
|
20
|
-
|
30
|
+
validate_id!(id, "List")
|
31
|
+
validate_list_entry_data!(data)
|
21
32
|
request(:post, "lists/#{id}/entries", data)
|
22
33
|
end
|
23
34
|
|
24
35
|
def get_entry(list_id:, entry_id:)
|
25
|
-
|
26
|
-
|
36
|
+
validate_id!(list_id, "List")
|
37
|
+
validate_id!(entry_id, "Entry")
|
27
38
|
request(:get, "lists/#{list_id}/entries/#{entry_id}")
|
28
39
|
end
|
29
40
|
|
30
41
|
def delete_entry(list_id:, entry_id:)
|
31
|
-
|
32
|
-
|
42
|
+
validate_id!(list_id, "List")
|
43
|
+
validate_id!(entry_id, "Entry")
|
33
44
|
request(:delete, "lists/#{list_id}/entries/#{entry_id}")
|
34
45
|
end
|
35
46
|
|
36
|
-
private
|
37
|
-
|
38
|
-
def validate_id!(id)
|
39
|
-
raise ArgumentError, "List ID is required" if id.nil? || id.to_s.strip.empty?
|
40
|
-
end
|
41
|
-
|
42
|
-
def validate_list_id!(list_id)
|
43
|
-
raise ArgumentError, "List ID is required" if list_id.nil? || list_id.to_s.strip.empty?
|
44
|
-
end
|
45
|
-
|
46
|
-
def validate_entry_id!(entry_id)
|
47
|
-
raise ArgumentError, "Entry ID is required" if entry_id.nil? || entry_id.to_s.strip.empty?
|
48
|
-
end
|
49
|
-
|
50
|
-
def validate_data!(data)
|
47
|
+
private def validate_list_entry_data!(data)
|
51
48
|
raise ArgumentError, "Data is required" if data.nil?
|
52
49
|
raise ArgumentError, "Data must be a hash" unless data.is_a?(Hash)
|
53
50
|
end
|
54
51
|
end
|
55
52
|
end
|
56
|
-
end
|
53
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Attio
|
4
|
+
module Resources
|
5
|
+
# API resource for managing notes in Attio
|
6
|
+
#
|
7
|
+
# Notes can be attached to records to track important information,
|
8
|
+
# meeting notes, or any other textual data.
|
9
|
+
#
|
10
|
+
# @example Creating a note on a person
|
11
|
+
# client.notes.create(
|
12
|
+
# parent_object: "people",
|
13
|
+
# parent_record_id: "person_123",
|
14
|
+
# title: "Meeting Notes",
|
15
|
+
# content: "Discussed Q4 goals..."
|
16
|
+
# )
|
17
|
+
#
|
18
|
+
# @example Listing notes for a record
|
19
|
+
# client.notes.list(
|
20
|
+
# parent_object: "companies",
|
21
|
+
# parent_record_id: "company_456"
|
22
|
+
# )
|
23
|
+
class Notes < Base
|
24
|
+
# List notes for a specific parent record
|
25
|
+
#
|
26
|
+
# @param parent_object [String] The parent object type (e.g., "people", "companies")
|
27
|
+
# @param parent_record_id [String] The ID of the parent record
|
28
|
+
# @param params [Hash] Additional query parameters
|
29
|
+
# @option params [Integer] :limit Number of notes to return
|
30
|
+
# @option params [String] :cursor Pagination cursor
|
31
|
+
#
|
32
|
+
# @return [Hash] API response containing notes
|
33
|
+
# @raise [ArgumentError] if parent_object or parent_record_id is nil
|
34
|
+
def list(parent_object:, parent_record_id:, **params)
|
35
|
+
validate_parent!(parent_object, parent_record_id)
|
36
|
+
request(:get, "notes", params.merge(
|
37
|
+
parent_object: parent_object,
|
38
|
+
parent_record_id: parent_record_id
|
39
|
+
))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get a specific note by ID
|
43
|
+
#
|
44
|
+
# @param id [String] The note ID
|
45
|
+
#
|
46
|
+
# @return [Hash] The note data
|
47
|
+
# @raise [ArgumentError] if id is nil or empty
|
48
|
+
def get(id:)
|
49
|
+
validate_id!(id, "Note")
|
50
|
+
request(:get, "notes/#{id}")
|
51
|
+
end
|
52
|
+
|
53
|
+
# Create a new note
|
54
|
+
#
|
55
|
+
# @param parent_object [String] The parent object type
|
56
|
+
# @param parent_record_id [String] The ID of the parent record
|
57
|
+
# @param title [String] The note title
|
58
|
+
# @param content [String] The note content (supports markdown)
|
59
|
+
# @param data [Hash] Additional note data
|
60
|
+
#
|
61
|
+
# @return [Hash] The created note
|
62
|
+
# @raise [ArgumentError] if required parameters are missing
|
63
|
+
def create(parent_object:, parent_record_id:, title:, content:, **data)
|
64
|
+
validate_parent!(parent_object, parent_record_id)
|
65
|
+
validate_required_string!(title, "Note title")
|
66
|
+
validate_required_string!(content, "Note content")
|
67
|
+
|
68
|
+
request(:post, "notes", data.merge(
|
69
|
+
parent_object: parent_object,
|
70
|
+
parent_record_id: parent_record_id,
|
71
|
+
title: title,
|
72
|
+
content: content
|
73
|
+
))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Update an existing note
|
77
|
+
#
|
78
|
+
# @param id [String] The note ID
|
79
|
+
# @param data [Hash] The fields to update
|
80
|
+
# @option data [String] :title New title
|
81
|
+
# @option data [String] :content New content
|
82
|
+
#
|
83
|
+
# @return [Hash] The updated note
|
84
|
+
# @raise [ArgumentError] if id or data is invalid
|
85
|
+
def update(id:, **data)
|
86
|
+
validate_id!(id, "Note")
|
87
|
+
validate_note_update_data!(data)
|
88
|
+
request(:patch, "notes/#{id}", data)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Delete a note
|
92
|
+
#
|
93
|
+
# @param id [String] The note ID to delete
|
94
|
+
#
|
95
|
+
# @return [Hash] Deletion confirmation
|
96
|
+
# @raise [ArgumentError] if id is nil or empty
|
97
|
+
def delete(id:)
|
98
|
+
validate_id!(id, "Note")
|
99
|
+
request(:delete, "notes/#{id}")
|
100
|
+
end
|
101
|
+
|
102
|
+
private def validate_note_update_data!(data)
|
103
|
+
raise ArgumentError, "Update data is required" if data.empty?
|
104
|
+
return if data.key?(:title) || data.key?(:content)
|
105
|
+
|
106
|
+
raise ArgumentError, "Must provide title or content to update"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -1,5 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attio
|
2
4
|
module Resources
|
5
|
+
# API resource for managing Attio objects
|
6
|
+
#
|
7
|
+
# Objects define the schema and structure for different types of
|
8
|
+
# records in your Attio workspace (e.g., people, companies).
|
9
|
+
#
|
10
|
+
# @example Listing all objects
|
11
|
+
# client.objects.list
|
3
12
|
class Objects < Base
|
4
13
|
def list(**params)
|
5
14
|
request(:get, "objects", params)
|
@@ -10,11 +19,9 @@ module Attio
|
|
10
19
|
request(:get, "objects/#{id_or_slug}")
|
11
20
|
end
|
12
21
|
|
13
|
-
private
|
14
|
-
|
15
|
-
def validate_id_or_slug!(id_or_slug)
|
22
|
+
private def validate_id_or_slug!(id_or_slug)
|
16
23
|
raise ArgumentError, "Object ID or slug is required" if id_or_slug.nil? || id_or_slug.to_s.strip.empty?
|
17
24
|
end
|
18
25
|
end
|
19
26
|
end
|
20
|
-
end
|
27
|
+
end
|