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/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 = [
|
10
|
-
t.options = [
|
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[
|
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 :
|
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")
|
51
|
+
FileUtils.rm_rf("docs")
|
50
52
|
puts "Documentation cleaned."
|
51
53
|
end
|
52
54
|
|
data/attio.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
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 =
|
10
|
-
spec.description =
|
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
|
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
|
-
|
11
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
84
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
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[
|
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:
|
20
|
+
object: "people",
|
20
21
|
filters: {
|
21
|
-
name: { contains:
|
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:
|
30
|
+
object: "people",
|
30
31
|
data: {
|
31
|
-
name:
|
32
|
-
email:
|
33
|
-
phone:
|
34
|
-
notes:
|
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:
|
42
|
-
id: new_person[
|
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:
|
49
|
-
id: person[
|
49
|
+
object: "people",
|
50
|
+
id: person["data"]["id"],
|
50
51
|
data: {
|
51
|
-
name:
|
52
|
-
notes:
|
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:
|
63
|
+
object: "companies",
|
63
64
|
data: {
|
64
|
-
name:
|
65
|
-
domain:
|
66
|
-
industry:
|
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:
|
74
|
-
id: person[
|
74
|
+
object: "people",
|
75
|
+
id: person["data"]["id"],
|
75
76
|
data: {
|
76
77
|
company: {
|
77
|
-
target_object:
|
78
|
-
target_record_id: company[
|
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:
|
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:
|
114
|
+
client.records.delete(object: "people", id: person["data"]["id"])
|
114
115
|
puts "Deleted person record"
|
115
116
|
|
116
|
-
client.records.delete(object:
|
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!"
|