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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +39 -15
  3. data/.github/workflows/coverage.yml +67 -0
  4. data/.github/workflows/pr_checks.yml +25 -7
  5. data/.github/workflows/release.yml +27 -13
  6. data/.github/workflows/tests.yml +67 -0
  7. data/.rubocop.yml +362 -90
  8. data/CHANGELOG.md +49 -1
  9. data/CONCEPTS.md +428 -0
  10. data/CONTRIBUTING.md +4 -4
  11. data/Gemfile +8 -5
  12. data/Gemfile.lock +4 -4
  13. data/README.md +164 -2
  14. data/Rakefile +8 -6
  15. data/attio.gemspec +6 -7
  16. data/danger/Dangerfile +22 -34
  17. data/docs/example.rb +30 -29
  18. data/examples/advanced_filtering.rb +178 -0
  19. data/examples/basic_usage.rb +110 -0
  20. data/examples/collaboration_example.rb +173 -0
  21. data/examples/full_workflow.rb +348 -0
  22. data/examples/notes_and_tasks.rb +200 -0
  23. data/lib/attio/client.rb +67 -29
  24. data/lib/attio/connection_pool.rb +26 -14
  25. data/lib/attio/errors.rb +4 -2
  26. data/lib/attio/http_client.rb +70 -41
  27. data/lib/attio/logger.rb +37 -27
  28. data/lib/attio/resources/attributes.rb +12 -5
  29. data/lib/attio/resources/base.rb +66 -27
  30. data/lib/attio/resources/comments.rb +147 -0
  31. data/lib/attio/resources/lists.rb +21 -24
  32. data/lib/attio/resources/notes.rb +110 -0
  33. data/lib/attio/resources/objects.rb +11 -4
  34. data/lib/attio/resources/records.rb +49 -67
  35. data/lib/attio/resources/tasks.rb +131 -0
  36. data/lib/attio/resources/threads.rb +154 -0
  37. data/lib/attio/resources/users.rb +10 -4
  38. data/lib/attio/resources/workspaces.rb +9 -1
  39. data/lib/attio/retry_handler.rb +19 -11
  40. data/lib/attio/version.rb +3 -1
  41. data/lib/attio.rb +15 -9
  42. metadata +13 -18
  43. data/run_tests.rb +0 -52
  44. data/test_basic.rb +0 -51
  45. data/test_typhoeus.rb +0 -31
data/Rakefile CHANGED
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rspec/core/rake_task"
3
5
 
4
6
  begin
5
7
  require "yard"
6
8
  require "yard/rake/yardoc_task"
7
-
9
+
8
10
  YARD::Rake::YardocTask.new do |t|
9
- t.files = ['lib/**/*.rb']
10
- t.options = ['--output-dir', 'docs']
11
+ t.files = ["lib/**/*.rb"]
12
+ t.options = ["--output-dir", "docs"]
11
13
  end
12
14
  rescue LoadError
13
15
  # YARD is not available
@@ -20,7 +22,7 @@ task default: :spec
20
22
  namespace :coverage do
21
23
  desc "Run tests with coverage report"
22
24
  task :report do
23
- ENV['COVERAGE'] = 'true'
25
+ ENV["COVERAGE"] = "true"
24
26
  Rake::Task["spec"].execute
25
27
  end
26
28
  end
@@ -36,7 +38,7 @@ namespace :docs do
36
38
  end
37
39
 
38
40
  desc "Generate and open documentation"
39
- task :open => :generate do
41
+ task open: :generate do
40
42
  if File.exist?("docs/index.html")
41
43
  system("open docs/index.html")
42
44
  else
@@ -46,7 +48,7 @@ namespace :docs do
46
48
 
47
49
  desc "Clean generated documentation"
48
50
  task :clean do
49
- FileUtils.rm_rf("docs") if File.exist?("docs")
51
+ FileUtils.rm_rf("docs")
50
52
  puts "Documentation cleaned."
51
53
  end
52
54
 
data/attio.gemspec CHANGED
@@ -1,4 +1,6 @@
1
- require_relative 'lib/attio/version'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/attio/version"
2
4
 
3
5
  Gem::Specification.new do |spec|
4
6
  spec.name = "attio"
@@ -6,8 +8,8 @@ Gem::Specification.new do |spec|
6
8
  spec.authors = ["Ernest Sim"]
7
9
  spec.email = ["ernest.codes@gmail.com"]
8
10
 
9
- spec.summary = %q{Ruby client for the Attio API}
10
- spec.description = %q{A Ruby library for interacting with the Attio API, providing easy access to CRM functionality}
11
+ spec.summary = "Ruby client for the Attio API"
12
+ spec.description = "A Ruby library for interacting with the Attio API, providing easy access to CRM functionality"
11
13
  spec.homepage = "https://github.com/idl3/attio"
12
14
  spec.license = "MIT"
13
15
  spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
@@ -20,7 +22,7 @@ Gem::Specification.new do |spec|
20
22
 
21
23
  # Specify which files should be added to the gem when it is released.
22
24
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
26
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
27
  end
26
28
  spec.bindir = "exe"
@@ -28,7 +30,4 @@ Gem::Specification.new do |spec|
28
30
  spec.require_paths = ["lib"]
29
31
 
30
32
  spec.add_dependency "typhoeus", "~> 1.4"
31
-
32
- # Development dependencies
33
- spec.add_development_dependency "yard", "~> 0.9"
34
33
  end
data/danger/Dangerfile CHANGED
@@ -7,13 +7,11 @@ warn("PR is classed as Work in Progress") if github.pr_title.include? "WIP"
7
7
  warn("Big PR") if git.lines_of_code > 500
8
8
 
9
9
  # Don't let testing shortcuts get into main by accident
10
- fail("fdescribe left in tests") if `grep -r fdescribe spec/ `.length > 1
11
- fail("fit left in tests") if `grep -r fit spec/ `.length > 1
10
+ raise("fdescribe left in tests") if `grep -r fdescribe spec/ `.length > 1
11
+ raise("fit left in tests") if `grep -r fit spec/ `.length > 1
12
12
 
13
13
  # Ensure a clean commit history
14
- if git.commits.any? { |c| c.message =~ /^fixup!/ }
15
- fail("Please squash fixup! commits before merging")
16
- end
14
+ raise("Please squash fixup! commits before merging") if git.commits.any? { |c| c.message =~ /^fixup!/ }
17
15
 
18
16
  # Check for proper conventional commit format
19
17
  if github.pr_title !~ /^(feat|fix|docs|style|refactor|perf|test|chore|ci|build)(\(.+\))?: .+/
@@ -27,27 +25,21 @@ end
27
25
 
28
26
  # Check if package files have been updated
29
27
  package_updated = git.modified_files.include?("attio.gemspec") || git.modified_files.include?("Gemfile")
30
- if package_updated
31
- message("📦 Package files have been updated")
32
- end
28
+ message("📦 Package files have been updated") if package_updated
33
29
 
34
30
  # Encourage changelog updates for non-trivial changes
35
31
  has_app_changes = git.modified_files.any? { |file| file.start_with?("lib/") }
36
32
  has_changelog_changes = git.modified_files.include?("CHANGELOG.md")
37
33
 
38
- if has_app_changes && !has_changelog_changes
34
+ if has_app_changes && !has_changelog_changes && github.pr_title !~ /^(chore|ci|docs|style|test):/
39
35
  # Skip for certain PR types
40
- unless github.pr_title =~ /^(chore|ci|docs|style|test):/
41
- warn("Consider updating CHANGELOG.md for this change")
42
- end
36
+ warn("Consider updating CHANGELOG.md for this change")
43
37
  end
44
38
 
45
39
  # Check for TODO comments in the diff
46
40
  git.diff.each do |file|
47
41
  file.patch.lines.each_with_index do |line, index|
48
- if line.start_with?("+") && line.include?("TODO")
49
- warn("TODO comment added", file: file.path, line: index + 1)
50
- end
42
+ warn("TODO comment added", file: file.path, line: index + 1) if line.start_with?("+") && line.include?("TODO")
51
43
  end
52
44
  end
53
45
 
@@ -55,9 +47,7 @@ end
55
47
  has_new_features = git.diff.any? { |file| file.patch.include?("+def ") && file.path.start_with?("lib/") }
56
48
  has_test_changes = git.modified_files.any? { |file| file.start_with?("spec/") }
57
49
 
58
- if has_new_features && !has_test_changes
59
- warn("New features should include tests")
60
- end
50
+ warn("New features should include tests") if has_new_features && !has_test_changes
61
51
 
62
52
  # Check for debugging code
63
53
  debugging_patterns = [
@@ -66,27 +56,25 @@ debugging_patterns = [
66
56
  "puts",
67
57
  "p ",
68
58
  "pp ",
69
- "console.log"
59
+ "console.log",
70
60
  ]
71
61
 
72
62
  git.diff.each do |file|
73
63
  debugging_patterns.each do |pattern|
74
64
  if file.patch.include?("+") && file.patch.include?(pattern)
75
- fail("Debugging code found: #{pattern} in #{file.path}")
65
+ raise("Debugging code found: #{pattern} in #{file.path}")
76
66
  end
77
67
  end
78
68
  end
79
69
 
80
70
  # Encourage documentation for public API changes
81
- public_api_changes = git.diff.any? do |file|
82
- file.path.start_with?("lib/") &&
83
- file.patch.include?("+ def ") &&
84
- !file.patch.include?("+ def self.") # Skip private class methods
71
+ public_api_changes = git.diff.any? do |file|
72
+ file.path.start_with?("lib/") &&
73
+ file.patch.include?("+ def ") &&
74
+ !file.patch.include?("+ def self.") # Skip private class methods
85
75
  end
86
76
 
87
- if public_api_changes
88
- message("📝 Public API changes detected. Consider updating documentation.")
89
- end
77
+ message("📝 Public API changes detected. Consider updating documentation.") if public_api_changes
90
78
 
91
79
  # Check for secrets or sensitive information
92
80
  sensitive_patterns = [
@@ -94,16 +82,16 @@ sensitive_patterns = [
94
82
  /secret/i,
95
83
  /password/i,
96
84
  /token/i,
97
- /auth/i
85
+ /auth/i,
98
86
  ]
99
87
 
100
88
  git.diff.each do |file|
101
89
  file.patch.lines.each_with_index do |line, index|
102
- if line.start_with?("+")
103
- sensitive_patterns.each do |pattern|
104
- if line.match?(pattern) && !line.include?("# ") # Not a comment
105
- warn("Potential sensitive information detected", file: file.path, line: index + 1)
106
- end
90
+ next unless line.start_with?("+")
91
+
92
+ sensitive_patterns.each do |pattern|
93
+ if line.match?(pattern) && !line.include?("# ") # Not a comment
94
+ warn("Potential sensitive information detected", file: file.path, line: index + 1)
107
95
  end
108
96
  end
109
97
  end
@@ -118,4 +106,4 @@ end
118
106
  # Remind about version bumping for releases
119
107
  if git.modified_files.include?("lib/attio/version.rb")
120
108
  message("🔖 Version file updated. Don't forget to update CHANGELOG.md and create a release tag.")
121
- end
109
+ end
data/docs/example.rb CHANGED
@@ -1,24 +1,25 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Example usage of the Attio Ruby client
4
- #
5
+ #
5
6
  # This file demonstrates common use cases and serves as
6
7
  # additional documentation for YARD.
7
8
 
8
- require_relative '../lib/attio'
9
+ require_relative "../lib/attio"
9
10
 
10
11
  # Initialize client with API key
11
12
  # In production, use environment variables or secure config
12
- client = Attio.client(api_key: ENV['ATTIO_API_KEY'] || 'your-api-key-here')
13
+ client = Attio.client(api_key: ENV["ATTIO_API_KEY"] || "your-api-key-here")
13
14
 
14
15
  # Example 1: Working with People Records
15
16
  puts "=== Working with People Records ==="
16
17
 
17
18
  # List people with filters
18
19
  people = client.records.list(
19
- object: 'people',
20
+ object: "people",
20
21
  filters: {
21
- name: { contains: 'John' }
22
+ name: { contains: "John" },
22
23
  },
23
24
  limit: 10
24
25
  )
@@ -26,30 +27,30 @@ puts "Found #{people['data'].length} people matching filter"
26
27
 
27
28
  # Create a new person
28
29
  new_person = client.records.create(
29
- object: 'people',
30
+ object: "people",
30
31
  data: {
31
- name: 'Jane Doe',
32
- email: 'jane.doe@example.com',
33
- phone: '+1-555-0123',
34
- notes: 'Created via Ruby client example'
32
+ name: "Jane Doe",
33
+ email: "jane.doe@example.com",
34
+ phone: "+1-555-0123",
35
+ notes: "Created via Ruby client example",
35
36
  }
36
37
  )
37
38
  puts "Created person: #{new_person['data']['name']} (ID: #{new_person['data']['id']})"
38
39
 
39
40
  # Get the person we just created
40
41
  person = client.records.get(
41
- object: 'people',
42
- id: new_person['data']['id']
42
+ object: "people",
43
+ id: new_person["data"]["id"]
43
44
  )
44
45
  puts "Retrieved person: #{person['data']['name']}"
45
46
 
46
47
  # Update the person
47
48
  updated_person = client.records.update(
48
- object: 'people',
49
- id: person['data']['id'],
49
+ object: "people",
50
+ id: person["data"]["id"],
50
51
  data: {
51
- name: 'Jane Smith',
52
- notes: 'Updated name after marriage'
52
+ name: "Jane Smith",
53
+ notes: "Updated name after marriage",
53
54
  }
54
55
  )
55
56
  puts "Updated person name to: #{updated_person['data']['name']}"
@@ -59,24 +60,24 @@ puts "\n=== Working with Company Records ==="
59
60
 
60
61
  # Create a company
61
62
  company = client.records.create(
62
- object: 'companies',
63
+ object: "companies",
63
64
  data: {
64
- name: 'Acme Corporation',
65
- domain: 'acme.com',
66
- industry: 'Technology'
65
+ name: "Acme Corporation",
66
+ domain: "acme.com",
67
+ industry: "Technology",
67
68
  }
68
69
  )
69
70
  puts "Created company: #{company['data']['name']}"
70
71
 
71
72
  # Link the person to the company
72
73
  client.records.update(
73
- object: 'people',
74
- id: person['data']['id'],
74
+ object: "people",
75
+ id: person["data"]["id"],
75
76
  data: {
76
77
  company: {
77
- target_object: 'companies',
78
- target_record_id: company['data']['id']
79
- }
78
+ target_object: "companies",
79
+ target_record_id: company["data"]["id"],
80
+ },
80
81
  }
81
82
  )
82
83
  puts "Linked #{person['data']['name']} to #{company['data']['name']}"
@@ -101,7 +102,7 @@ puts "\n=== Error Handling Example ==="
101
102
 
102
103
  begin
103
104
  # Try to get a non-existent record
104
- client.records.get(object: 'people', id: 'non-existent-id')
105
+ client.records.get(object: "people", id: "non-existent-id")
105
106
  rescue Attio::NotFoundError => e
106
107
  puts "Caught expected error: #{e.message}"
107
108
  rescue Attio::APIError => e
@@ -110,10 +111,10 @@ end
110
111
 
111
112
  # Clean up - delete the records we created
112
113
  puts "\n=== Cleanup ==="
113
- client.records.delete(object: 'people', id: person['data']['id'])
114
+ client.records.delete(object: "people", id: person["data"]["id"])
114
115
  puts "Deleted person record"
115
116
 
116
- client.records.delete(object: 'companies', id: company['data']['id'])
117
+ client.records.delete(object: "companies", id: company["data"]["id"])
117
118
  puts "Deleted company record"
118
119
 
119
- puts "\nExample completed successfully!"
120
+ puts "\nExample completed successfully!"
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "attio"
5
+ require "json"
6
+
7
+ # Example: Advanced Filtering and Querying
8
+ #
9
+ # This example demonstrates how to use advanced filtering and sorting
10
+ # capabilities when querying records in Attio.
11
+
12
+ # Initialize the client
13
+ client = Attio.client(api_key: ENV.fetch("ATTIO_API_KEY"))
14
+
15
+ puts "Attio Advanced Filtering Example"
16
+ puts "=" * 40
17
+
18
+ # 1. Query with simple filters
19
+ puts "\n1. Finding people by email domain..."
20
+ people_from_domain = client.records.list(
21
+ object: "people",
22
+ filters: {
23
+ email: { contains: "@example.com" },
24
+ },
25
+ limit: 10
26
+ )
27
+ puts " Found #{people_from_domain['data']&.length || 0} people from example.com"
28
+
29
+ # 2. Query with multiple filters (AND condition)
30
+ puts "\n2. Finding high-value companies in technology sector..."
31
+ high_value_tech = client.records.list(
32
+ object: "companies",
33
+ filters: {
34
+ industry: { equals: "Technology" },
35
+ annual_revenue: { greater_than: 1_000_000 },
36
+ },
37
+ sorts: [
38
+ { field: "annual_revenue", direction: "desc" },
39
+ ],
40
+ limit: 5
41
+ )
42
+ puts " Found #{high_value_tech['data']&.length || 0} high-value tech companies"
43
+
44
+ # 3. Query with date range filters
45
+ puts "\n3. Finding recently created records..."
46
+ recent_date = (Date.today - 30).iso8601
47
+ recent_records = client.records.list(
48
+ object: "people",
49
+ filters: {
50
+ created_at: { greater_than: recent_date },
51
+ },
52
+ sorts: [
53
+ { field: "created_at", direction: "desc" },
54
+ ]
55
+ )
56
+ puts " Found #{recent_records['data']&.length || 0} people created in last 30 days"
57
+
58
+ # 4. Query with relationship filters
59
+ puts "\n4. Finding people associated with specific companies..."
60
+ # First, get a company
61
+ companies = client.records.list(object: "companies", limit: 1)
62
+ if companies.dig("data", 0)
63
+ company_id = companies.dig("data", 0, "id", "record_id")
64
+
65
+ people_at_company = client.records.list(
66
+ object: "people",
67
+ filters: {
68
+ company: {
69
+ target_object: "companies",
70
+ target_record_id: company_id,
71
+ },
72
+ }
73
+ )
74
+ puts " Found #{people_at_company['data']&.length || 0} people at the company"
75
+ else
76
+ puts " No companies found for demo"
77
+ end
78
+
79
+ # 5. Query with null/not null filters
80
+ puts "\n5. Finding records with missing data..."
81
+ missing_email = client.records.list(
82
+ object: "people",
83
+ filters: {
84
+ email: { is_null: true },
85
+ },
86
+ limit: 10
87
+ )
88
+ puts " Found #{missing_email['data']&.length || 0} people without email addresses"
89
+
90
+ # 6. Complex sorting with multiple fields
91
+ puts "\n6. Sorting by multiple criteria..."
92
+ sorted_companies = client.records.list(
93
+ object: "companies",
94
+ sorts: [
95
+ { field: "industry", direction: "asc" },
96
+ { field: "annual_revenue", direction: "desc" },
97
+ { field: "name", direction: "asc" },
98
+ ],
99
+ limit: 20
100
+ )
101
+ puts " Retrieved #{sorted_companies['data']&.length || 0} companies sorted by industry, revenue, and name"
102
+
103
+ # 7. Pagination example
104
+ puts "\n7. Paginating through results..."
105
+ page_size = 5
106
+ total_fetched = 0
107
+ cursor = nil
108
+
109
+ 3.times do |page|
110
+ params = {
111
+ object: "people",
112
+ limit: page_size,
113
+ }
114
+ params[:cursor] = cursor if cursor
115
+
116
+ page_results = client.records.list(**params)
117
+ fetched = page_results["data"]&.length || 0
118
+ total_fetched += fetched
119
+
120
+ puts " Page #{page + 1}: fetched #{fetched} records"
121
+
122
+ cursor = page_results.dig("pagination", "next_cursor")
123
+ break unless cursor
124
+ end
125
+ puts " Total fetched across pages: #{total_fetched}"
126
+
127
+ # 8. Query tasks with status filter
128
+ puts "\n8. Finding pending tasks..."
129
+ pending_tasks = client.tasks.list(
130
+ status: "pending",
131
+ limit: 10
132
+ )
133
+ puts " Found #{pending_tasks['data']&.length || 0} pending tasks"
134
+
135
+ # 9. Query with custom field filters
136
+ puts "\n9. Filtering by custom fields..."
137
+ # This assumes you have custom fields set up
138
+ client.records.list(
139
+ object: "people",
140
+ filters: {
141
+ # Replace with your actual custom field API slug
142
+ # custom_field: { equals: "some_value" }
143
+ },
144
+ limit: 10
145
+ )
146
+ puts " Custom field filtering available for your specific schema"
147
+
148
+ # 10. Export query for analysis
149
+ puts "\n10. Exporting query results..."
150
+ export_data = client.records.list(
151
+ object: "companies",
152
+ filters: {
153
+ industry: { not_null: true },
154
+ },
155
+ limit: 100
156
+ )
157
+
158
+ if export_data["data"] && !export_data["data"].empty?
159
+ # Simple CSV-like export
160
+ puts " Sample export (first 3 records):"
161
+ export_data["data"].take(3).each do |record|
162
+ name = record.dig("values", "name", 0, "value") || "N/A"
163
+ industry = record.dig("values", "industry", 0, "value") || "N/A"
164
+ puts " - #{name}: #{industry}"
165
+ end
166
+ end
167
+
168
+ puts "\n#{'=' * 40}"
169
+ puts "Example completed successfully!"
170
+ puts "\nThis example demonstrated:"
171
+ puts " • Simple and complex filtering"
172
+ puts " • Multi-field sorting"
173
+ puts " • Date range queries"
174
+ puts " • Relationship filters"
175
+ puts " • Null/not-null checks"
176
+ puts " • Pagination"
177
+ puts " • Task filtering"
178
+ puts " • Custom field queries"
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "attio"
6
+
7
+ # Basic usage example for the Attio Ruby gem
8
+ #
9
+ # This example demonstrates:
10
+ # - Client initialization
11
+ # - Working with records (people, companies)
12
+ # - Creating relationships between records
13
+ # - Error handling
14
+
15
+ # Initialize the client with your API key
16
+ # You can get your API key from: https://app.attio.com/settings/api-keys
17
+ client = Attio.client(api_key: ENV.fetch("ATTIO_API_KEY"))
18
+
19
+ puts "🚀 Attio Ruby Client - Basic Usage Example"
20
+ puts "=" * 50
21
+
22
+ begin
23
+ # List all available objects in your workspace
24
+ puts "\n📋 Available Objects:"
25
+ objects = client.objects.list
26
+ objects["data"].each do |object|
27
+ puts " - #{object['name']} (#{object['id']})"
28
+ end
29
+
30
+ # Working with People records
31
+ puts "\n👥 Working with People:"
32
+
33
+ # List existing people (first 5)
34
+ people = client.records.list(object: "people", limit: 5)
35
+ puts " Found #{people['data'].length} people records"
36
+
37
+ # Create a new person
38
+ new_person = client.records.create(
39
+ object: "people",
40
+ data: {
41
+ name: "John Doe",
42
+ email: "john.doe@example.com",
43
+ phone: "+1-555-0123",
44
+ title: "Software Engineer",
45
+ }
46
+ )
47
+ puts " ✅ Created person: #{new_person['data']['name']} (ID: #{new_person['data']['id']})"
48
+
49
+ # Update the person
50
+ updated_person = client.records.update(
51
+ object: "people",
52
+ id: new_person["data"]["id"],
53
+ data: { title: "Senior Software Engineer" }
54
+ )
55
+ puts " ✅ Updated title to: #{updated_person['data']['title']}"
56
+
57
+ # Working with Companies
58
+ puts "\n🏢 Working with Companies:"
59
+
60
+ # Create a company
61
+ new_company = client.records.create(
62
+ object: "companies",
63
+ data: {
64
+ name: "Acme Corp",
65
+ domain: "acme.com",
66
+ employee_count: 100,
67
+ description: "Leading provider of innovative solutions",
68
+ }
69
+ )
70
+ puts " ✅ Created company: #{new_company['data']['name']}"
71
+
72
+ # Link person to company (create relationship)
73
+ # Note: This requires the person and company to have a relationship field configured
74
+ puts "\n🔗 Creating Relationships:"
75
+ # This would typically be done through a reference field
76
+ # The exact implementation depends on your Attio workspace configuration
77
+
78
+ # Working with Lists
79
+ puts "\n📝 Working with Lists:"
80
+ lists = client.lists.list(limit: 5)
81
+ if lists["data"].any?
82
+ first_list = lists["data"].first
83
+ puts " Found list: #{first_list['name']}"
84
+
85
+ # Get entries in the list
86
+ entries = client.lists.entries(id: first_list["id"], limit: 5)
87
+ puts " List has #{entries['data'].length} entries"
88
+ else
89
+ puts " No lists found in workspace"
90
+ end
91
+
92
+ # Cleanup - Delete the test records
93
+ puts "\n🧹 Cleaning up test data:"
94
+ client.records.delete(object: "people", id: new_person["data"]["id"])
95
+ puts " ✅ Deleted test person record"
96
+
97
+ client.records.delete(object: "companies", id: new_company["data"]["id"])
98
+ puts " ✅ Deleted test company record"
99
+ rescue Attio::AuthenticationError => e
100
+ puts "❌ Authentication failed: #{e.message}"
101
+ puts " Please check your API key"
102
+ rescue Attio::NotFoundError => e
103
+ puts "❌ Resource not found: #{e.message}"
104
+ rescue Attio::ValidationError => e
105
+ puts "❌ Validation error: #{e.message}"
106
+ rescue Attio::Error => e
107
+ puts "❌ API error: #{e.message}"
108
+ end
109
+
110
+ puts "\n✨ Example completed!"