attio 0.1.3 → 0.3.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.
@@ -0,0 +1,290 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Attio
4
+ module Resources
5
+ # Bulk operations for efficient batch processing
6
+ #
7
+ # @example Bulk create records
8
+ # client.bulk.create_records(
9
+ # object: "companies",
10
+ # records: [
11
+ # { name: "Acme Corp", domain: "acme.com" },
12
+ # { name: "Tech Co", domain: "techco.com" }
13
+ # ]
14
+ # )
15
+ #
16
+ # @example Bulk update records
17
+ # client.bulk.update_records(
18
+ # object: "people",
19
+ # updates: [
20
+ # { id: "person_123", data: { title: "CEO" } },
21
+ # { id: "person_456", data: { title: "CTO" } }
22
+ # ]
23
+ # )
24
+ class Bulk < Base
25
+ # Maximum number of records per bulk operation
26
+ MAX_BATCH_SIZE = 100
27
+
28
+ # Bulk create multiple records
29
+ #
30
+ # @param object [String] The object type (companies, people, etc.)
31
+ # @param records [Array<Hash>] Array of record data to create
32
+ # @param options [Hash] Additional options
33
+ # @option options [Boolean] :partial_success Allow partial success (default: false)
34
+ # @option options [Boolean] :return_records Return created records (default: true)
35
+ # @return [Hash] Results including created records and any errors
36
+ def create_records(object:, records:, options: {})
37
+ validate_required_string!(object, "Object type")
38
+ validate_bulk_records!(records, "create")
39
+
40
+ batches = records.each_slice(MAX_BATCH_SIZE).to_a
41
+ results = []
42
+
43
+ batches.each_with_index do |batch, index|
44
+ body = {
45
+ records: batch.map { |record| { data: record } },
46
+ partial_success: options.fetch(:partial_success, false),
47
+ return_records: options.fetch(:return_records, true),
48
+ }
49
+
50
+ result = request(:post, "objects/#{object}/records/bulk", body)
51
+ results << result.merge("batch" => index + 1)
52
+ end
53
+
54
+ merge_batch_results(results)
55
+ end
56
+
57
+ # Bulk update multiple records
58
+ #
59
+ # @param object [String] The object type
60
+ # @param updates [Array<Hash>] Array of updates with :id and :data keys
61
+ # @param options [Hash] Additional options
62
+ # @option options [Boolean] :partial_success Allow partial success (default: false)
63
+ # @option options [Boolean] :return_records Return updated records (default: true)
64
+ # @return [Hash] Results including updated records and any errors
65
+ def update_records(object:, updates:, options: {})
66
+ validate_required_string!(object, "Object type")
67
+ validate_bulk_updates!(updates)
68
+
69
+ batches = updates.each_slice(MAX_BATCH_SIZE).to_a
70
+ results = []
71
+
72
+ batches.each_with_index do |batch, index|
73
+ body = {
74
+ updates: batch,
75
+ partial_success: options.fetch(:partial_success, false),
76
+ return_records: options.fetch(:return_records, true),
77
+ }
78
+
79
+ result = request(:patch, "objects/#{object}/records/bulk", body)
80
+ results << result.merge("batch" => index + 1)
81
+ end
82
+
83
+ merge_batch_results(results)
84
+ end
85
+
86
+ # Bulk delete multiple records
87
+ #
88
+ # @param object [String] The object type
89
+ # @param ids [Array<String>] Array of record IDs to delete
90
+ # @param options [Hash] Additional options
91
+ # @option options [Boolean] :partial_success Allow partial success (default: false)
92
+ # @return [Hash] Results including deletion confirmations and any errors
93
+ def delete_records(object:, ids:, options: {})
94
+ validate_required_string!(object, "Object type")
95
+ validate_bulk_ids!(ids)
96
+
97
+ batches = ids.each_slice(MAX_BATCH_SIZE).to_a
98
+ results = []
99
+
100
+ batches.each_with_index do |batch, index|
101
+ body = {
102
+ ids: batch,
103
+ partial_success: options.fetch(:partial_success, false),
104
+ }
105
+
106
+ result = request(:delete, "objects/#{object}/records/bulk", body)
107
+ results << result.merge("batch" => index + 1)
108
+ end
109
+
110
+ merge_batch_results(results)
111
+ end
112
+
113
+ # Bulk upsert records (create or update based on matching criteria)
114
+ #
115
+ # @param object [String] The object type
116
+ # @param records [Array<Hash>] Records to upsert
117
+ # @param match_attribute [String] Attribute to match on (e.g., "email", "domain")
118
+ # @param options [Hash] Additional options
119
+ # @return [Hash] Results including created/updated records
120
+ def upsert_records(object:, records:, match_attribute:, options: {})
121
+ validate_required_string!(object, "Object type")
122
+ validate_required_string!(match_attribute, "Match attribute")
123
+ validate_bulk_records!(records, "upsert")
124
+
125
+ batches = records.each_slice(MAX_BATCH_SIZE).to_a
126
+ results = []
127
+
128
+ batches.each_with_index do |batch, index|
129
+ body = {
130
+ records: batch.map { |record| { data: record } },
131
+ match_attribute: match_attribute,
132
+ partial_success: options.fetch(:partial_success, false),
133
+ return_records: options.fetch(:return_records, true),
134
+ }
135
+
136
+ result = request(:put, "objects/#{object}/records/bulk", body)
137
+ results << result.merge("batch" => index + 1)
138
+ end
139
+
140
+ merge_batch_results(results)
141
+ end
142
+
143
+ # Bulk add entries to a list
144
+ #
145
+ # @param list_id [String] The list ID
146
+ # @param entries [Array<Hash>] Array of entries to add
147
+ # @param options [Hash] Additional options
148
+ # @return [Hash] Results including added entries
149
+ def add_list_entries(list_id:, entries:, options: {})
150
+ validate_id!(list_id, "List")
151
+ validate_bulk_records!(entries, "add to list")
152
+
153
+ batches = entries.each_slice(MAX_BATCH_SIZE).to_a
154
+ results = []
155
+
156
+ batches.each_with_index do |batch, index|
157
+ body = {
158
+ entries: batch,
159
+ partial_success: options.fetch(:partial_success, false),
160
+ }
161
+
162
+ result = request(:post, "lists/#{list_id}/entries/bulk", body)
163
+ results << result.merge("batch" => index + 1)
164
+ end
165
+
166
+ merge_batch_results(results)
167
+ end
168
+
169
+ # Bulk remove entries from a list
170
+ #
171
+ # @param list_id [String] The list ID
172
+ # @param entry_ids [Array<String>] Array of entry IDs to remove
173
+ # @param options [Hash] Additional options
174
+ # @return [Hash] Results including removal confirmations
175
+ def remove_list_entries(list_id:, entry_ids:, options: {})
176
+ validate_id!(list_id, "List")
177
+ validate_bulk_ids!(entry_ids)
178
+
179
+ batches = entry_ids.each_slice(MAX_BATCH_SIZE).to_a
180
+ results = []
181
+
182
+ batches.each_with_index do |batch, index|
183
+ body = {
184
+ entry_ids: batch,
185
+ partial_success: options.fetch(:partial_success, false),
186
+ }
187
+
188
+ result = request(:delete, "lists/#{list_id}/entries/bulk", body)
189
+ results << result.merge("batch" => index + 1)
190
+ end
191
+
192
+ merge_batch_results(results)
193
+ end
194
+
195
+ private def validate_bulk_records!(records, operation)
196
+ raise ArgumentError, "Records array is required for bulk #{operation}" if records.nil?
197
+ raise ArgumentError, "Records must be an array for bulk #{operation}" unless records.is_a?(Array)
198
+ raise ArgumentError, "Records array cannot be empty for bulk #{operation}" if records.empty?
199
+ raise ArgumentError, "Too many records (max #{MAX_BATCH_SIZE * 10})" if records.size > MAX_BATCH_SIZE * 10
200
+
201
+ records.each_with_index do |record, index|
202
+ raise ArgumentError, "Record at index #{index} must be a hash" unless record.is_a?(Hash)
203
+ end
204
+ end
205
+
206
+ private def validate_bulk_updates!(updates)
207
+ validate_array!(updates, "Updates", "bulk update")
208
+ validate_max_size!(updates, "updates")
209
+
210
+ updates.each_with_index do |update, index|
211
+ validate_update_item!(update, index)
212
+ end
213
+ end
214
+
215
+ private def validate_update_item!(update, index)
216
+ raise ArgumentError, "Update at index #{index} must be a hash" unless update.is_a?(Hash)
217
+ raise ArgumentError, "Update at index #{index} must have an :id" unless update[:id]
218
+ raise ArgumentError, "Update at index #{index} must have :data" unless update[:data]
219
+ end
220
+
221
+ private def validate_bulk_ids!(ids)
222
+ validate_array!(ids, "IDs", "bulk operation")
223
+ validate_max_size!(ids, "IDs")
224
+
225
+ ids.each_with_index do |id, index|
226
+ validate_id_item!(id, index)
227
+ end
228
+ end
229
+
230
+ private def validate_id_item!(id, index)
231
+ return unless id.nil? || id.to_s.strip.empty?
232
+
233
+ raise ArgumentError, "ID at index #{index} cannot be nil or empty"
234
+ end
235
+
236
+ private def validate_array!(array, name, operation)
237
+ raise ArgumentError, "#{name} array is required for #{operation}" if array.nil?
238
+ raise ArgumentError, "#{name} must be an array for #{operation}" unless array.is_a?(Array)
239
+ raise ArgumentError, "#{name} array cannot be empty for #{operation}" if array.empty?
240
+ end
241
+
242
+ private def validate_max_size!(array, name)
243
+ max = MAX_BATCH_SIZE * 10
244
+ return unless array.size > max
245
+
246
+ raise ArgumentError, "Too many #{name} (max #{max})"
247
+ end
248
+
249
+ private def merge_batch_results(results)
250
+ merged = initialize_merged_result(results.size)
251
+
252
+ results.each do |result|
253
+ merge_single_result!(merged, result)
254
+ end
255
+
256
+ merged
257
+ end
258
+
259
+ private def initialize_merged_result(batch_count)
260
+ {
261
+ "success" => true,
262
+ "total_batches" => batch_count,
263
+ "records" => [],
264
+ "errors" => [],
265
+ "statistics" => {
266
+ "created" => 0,
267
+ "updated" => 0,
268
+ "deleted" => 0,
269
+ "failed" => 0,
270
+ },
271
+ }
272
+ end
273
+
274
+ private def merge_single_result!(merged, result)
275
+ merged["records"].concat(result["records"] || [])
276
+ merged["errors"].concat(result["errors"] || [])
277
+ merge_statistics!(merged["statistics"], result["statistics"])
278
+ merged["success"] &&= result["success"] != false
279
+ end
280
+
281
+ private def merge_statistics!(target, source)
282
+ return unless source
283
+
284
+ %w[created updated deleted failed].each do |key|
285
+ target[key] += source[key] || 0
286
+ end
287
+ end
288
+ end
289
+ end
290
+ 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
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Attio
4
+ module Resources
5
+ # Deals resource for managing sales opportunities
6
+ #
7
+ # @example List all deals
8
+ # client.deals.list
9
+ #
10
+ # @example Create a new deal
11
+ # client.deals.create(
12
+ # data: {
13
+ # name: "Enterprise Contract",
14
+ # value: 50000,
15
+ # stage_id: "stage_123",
16
+ # company_id: "company_456"
17
+ # }
18
+ # )
19
+ #
20
+ # @example Update deal stage
21
+ # client.deals.update_stage(id: "deal_123", stage_id: "stage_won")
22
+ class Deals < Base
23
+ # List all deals
24
+ #
25
+ # @param params [Hash] Optional query parameters
26
+ # @option params [Hash] :filter Filter conditions
27
+ # @option params [Array<Hash>] :sorts Sort criteria
28
+ # @option params [Integer] :limit Maximum number of results
29
+ # @option params [String] :offset Pagination offset
30
+ # @return [Hash] The API response
31
+ def list(params = {})
32
+ request(:get, "objects/deals/records", params)
33
+ end
34
+
35
+ # Get a specific deal
36
+ #
37
+ # @param id [String] The deal ID
38
+ # @return [Hash] The deal data
39
+ def get(id:)
40
+ validate_id!(id, "Deal")
41
+ request(:get, "objects/deals/records/#{id}")
42
+ end
43
+
44
+ # Create a new deal
45
+ #
46
+ # @param data [Hash] The deal data
47
+ # @option data [String] :name The deal name (required)
48
+ # @option data [Float] :value The deal value
49
+ # @option data [String] :stage_id The stage ID
50
+ # @option data [String] :company_id Associated company ID
51
+ # @option data [String] :owner_id The owner user ID
52
+ # @option data [Date] :close_date Expected close date
53
+ # @option data [String] :currency Currency code (USD, EUR, etc.)
54
+ # @option data [Float] :probability Win probability (0-100)
55
+ # @return [Hash] The created deal
56
+ def create(data:)
57
+ validate_required_hash!(data, "Data")
58
+ validate_required_string!(data[:name], "Deal name")
59
+
60
+ request(:post, "objects/deals/records", { data: data })
61
+ end
62
+
63
+ # Update a deal
64
+ #
65
+ # @param id [String] The deal ID to update
66
+ # @param data [Hash] The data to update
67
+ # @return [Hash] The updated deal
68
+ def update(id:, data:)
69
+ validate_id!(id, "Deal")
70
+ validate_required_hash!(data, "Data")
71
+
72
+ request(:patch, "objects/deals/records/#{id}", { data: data })
73
+ end
74
+
75
+ # Delete a deal
76
+ #
77
+ # @param id [String] The deal ID to delete
78
+ # @return [Hash] Confirmation of deletion
79
+ def delete(id:)
80
+ validate_id!(id, "Deal")
81
+ request(:delete, "objects/deals/records/#{id}")
82
+ end
83
+
84
+ # Update a deal's stage
85
+ #
86
+ # @param id [String] The deal ID
87
+ # @param stage_id [String] The new stage ID
88
+ # @return [Hash] The updated deal
89
+ def update_stage(id:, stage_id:)
90
+ validate_id!(id, "Deal")
91
+ validate_required_string!(stage_id, "Stage")
92
+
93
+ update(id: id, data: { stage_id: stage_id })
94
+ end
95
+
96
+ # Mark a deal as won
97
+ #
98
+ # @param id [String] The deal ID
99
+ # @param won_date [Date, String] The date the deal was won (defaults to today)
100
+ # @param actual_value [Float] The actual value (optional, defaults to deal value)
101
+ # @return [Hash] The updated deal
102
+ def mark_won(id:, won_date: nil, actual_value: nil)
103
+ validate_id!(id, "Deal")
104
+
105
+ data = { status: "won" }
106
+ data[:won_date] = won_date if won_date
107
+ data[:actual_value] = actual_value if actual_value
108
+
109
+ update(id: id, data: data)
110
+ end
111
+
112
+ # Mark a deal as lost
113
+ #
114
+ # @param id [String] The deal ID
115
+ # @param lost_reason [String] The reason for losing the deal
116
+ # @param lost_date [Date, String] The date the deal was lost (defaults to today)
117
+ # @return [Hash] The updated deal
118
+ def mark_lost(id:, lost_reason: nil, lost_date: nil)
119
+ validate_id!(id, "Deal")
120
+
121
+ data = { status: "lost" }
122
+ data[:lost_reason] = lost_reason if lost_reason
123
+ data[:lost_date] = lost_date if lost_date
124
+
125
+ update(id: id, data: data)
126
+ end
127
+
128
+ # List deals by stage
129
+ #
130
+ # @param stage_id [String] The stage ID to filter by
131
+ # @param params [Hash] Additional query parameters
132
+ # @return [Hash] Deals in the specified stage
133
+ def list_by_stage(stage_id:, params: {})
134
+ validate_required_string!(stage_id, "Stage")
135
+
136
+ filter = { stage_id: { "$eq" => stage_id } }
137
+ merged_params = params.merge(filter: filter)
138
+ list(merged_params)
139
+ end
140
+
141
+ # List deals by company
142
+ #
143
+ # @param company_id [String] The company ID to filter by
144
+ # @param params [Hash] Additional query parameters
145
+ # @return [Hash] Deals for the specified company
146
+ def list_by_company(company_id:, params: {})
147
+ validate_required_string!(company_id, "Company")
148
+
149
+ filter = { company_id: { "$eq" => company_id } }
150
+ merged_params = params.merge(filter: filter)
151
+ list(merged_params)
152
+ end
153
+
154
+ # List deals by owner
155
+ #
156
+ # @param owner_id [String] The owner user ID to filter by
157
+ # @param params [Hash] Additional query parameters
158
+ # @return [Hash] Deals owned by the specified user
159
+ def list_by_owner(owner_id:, params: {})
160
+ validate_required_string!(owner_id, "Owner")
161
+
162
+ filter = { owner_id: { "$eq" => owner_id } }
163
+ merged_params = params.merge(filter: filter)
164
+ list(merged_params)
165
+ end
166
+
167
+ # Calculate pipeline value
168
+ #
169
+ # @param stage_id [String] Optional stage ID to filter by
170
+ # @param owner_id [String] Optional owner ID to filter by
171
+ # @return [Hash] Pipeline statistics including total value, count, and average
172
+ def pipeline_value(stage_id: nil, owner_id: nil)
173
+ params = { filter: {} }
174
+ params[:filter][:stage_id] = { "$eq" => stage_id } if stage_id
175
+ params[:filter][:owner_id] = { "$eq" => owner_id } if owner_id
176
+
177
+ # This would typically be a specialized endpoint, but we'll use list
178
+ # and let the client calculate the statistics
179
+ list(params)
180
+ end
181
+ end
182
+ end
183
+ end
@@ -17,46 +17,34 @@ module Attio
17
17
  end
18
18
 
19
19
  def get(id:)
20
- validate_id!(id)
20
+ validate_id!(id, "List")
21
21
  request(:get, "lists/#{id}")
22
22
  end
23
23
 
24
24
  def entries(id:, **params)
25
- validate_id!(id)
25
+ validate_id!(id, "List")
26
26
  request(:get, "lists/#{id}/entries", params)
27
27
  end
28
28
 
29
29
  def create_entry(id:, data:)
30
- validate_id!(id)
31
- validate_data!(data)
30
+ validate_id!(id, "List")
31
+ validate_list_entry_data!(data)
32
32
  request(:post, "lists/#{id}/entries", data)
33
33
  end
34
34
 
35
35
  def get_entry(list_id:, entry_id:)
36
- validate_list_id!(list_id)
37
- validate_entry_id!(entry_id)
36
+ validate_id!(list_id, "List")
37
+ validate_id!(entry_id, "Entry")
38
38
  request(:get, "lists/#{list_id}/entries/#{entry_id}")
39
39
  end
40
40
 
41
41
  def delete_entry(list_id:, entry_id:)
42
- validate_list_id!(list_id)
43
- validate_entry_id!(entry_id)
42
+ validate_id!(list_id, "List")
43
+ validate_id!(entry_id, "Entry")
44
44
  request(:delete, "lists/#{list_id}/entries/#{entry_id}")
45
45
  end
46
46
 
47
- private def validate_id!(id)
48
- raise ArgumentError, "List ID is required" if id.nil? || id.to_s.strip.empty?
49
- end
50
-
51
- private def validate_list_id!(list_id)
52
- raise ArgumentError, "List ID is required" if list_id.nil? || list_id.to_s.strip.empty?
53
- end
54
-
55
- private def validate_entry_id!(entry_id)
56
- raise ArgumentError, "Entry ID is required" if entry_id.nil? || entry_id.to_s.strip.empty?
57
- end
58
-
59
- private def validate_data!(data)
47
+ private def validate_list_entry_data!(data)
60
48
  raise ArgumentError, "Data is required" if data.nil?
61
49
  raise ArgumentError, "Data must be a hash" unless data.is_a?(Hash)
62
50
  end