attio-ruby 0.1.0 → 0.1.2

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.
data/examples/deals.rb ADDED
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "attio"
6
+
7
+ # This example demonstrates working with Deals in Attio
8
+
9
+ # Setup
10
+ Attio.api_key = ENV["ATTIO_API_KEY"] || raise("Please set ATTIO_API_KEY")
11
+ owner_email = ENV["ATTIO_TEST_USER_EMAIL"] || "sales@example.com"
12
+
13
+ puts "=== Creating Deals ==="
14
+
15
+ # Create a basic deal
16
+ deal = Attio::Deal.create(
17
+ name: "Enterprise Software Deal",
18
+ value: 50000,
19
+ stage: "In Progress",
20
+ owner: owner_email
21
+ )
22
+
23
+ puts "Created deal: #{deal.name}"
24
+ puts "Value: $#{deal.value["currency_value"]}"
25
+ puts "Stage: #{deal.stage["status"]["title"]}"
26
+
27
+ # Create a deal with associations
28
+ partnership_deal = Attio::Deal.create(
29
+ name: "Partnership Deal",
30
+ value: 100000,
31
+ stage: "Lead",
32
+ owner: owner_email,
33
+ associated_people: ["partner@example.com", "contact@partner.com"],
34
+ associated_company: ["partner.com"]
35
+ )
36
+
37
+ puts "\nCreated partnership deal with associations"
38
+
39
+ puts "\n=== Searching Deals ==="
40
+
41
+ # Find high-value deals
42
+ big_deals = Attio::Deal.find_by_value_range(min: 75000)
43
+ puts "Found #{big_deals.count} deals worth $75k+"
44
+
45
+ # Find deals in a value range
46
+ mid_deals = Attio::Deal.find_by_value_range(min: 25000, max: 75000)
47
+ puts "Found #{mid_deals.count} deals worth $25k-$75k"
48
+
49
+ # Find deals by stage
50
+ in_progress = Attio::Deal.find_by(stage: "In Progress")
51
+ puts "Found #{in_progress ? 1 : 0} deals in progress"
52
+
53
+ puts "\n=== Updating Deals ==="
54
+
55
+ # Update deal stage
56
+ deal.update_stage("Won 🎉")
57
+ puts "Updated deal stage to: Won 🎉"
58
+
59
+ # Update deal value
60
+ deal.update_value(75000)
61
+ puts "Updated deal value to: $75,000"
62
+
63
+ # Check deal status
64
+ puts "\nDeal status checks:"
65
+ puts "Is open? #{deal.open?}"
66
+ puts "Is won? #{deal.won?}"
67
+ puts "Is lost? #{deal.lost?}"
68
+
69
+ puts "\n=== Deal Pipeline Analysis ==="
70
+
71
+ # List all deals and analyze by stage
72
+ all_deals = Attio::Deal.list(params: { limit: 50 })
73
+ stage_counts = Hash.new(0)
74
+
75
+ all_deals.each do |d|
76
+ stage_title = d.stage.dig("status", "title") if d.stage.is_a?(Hash)
77
+ stage_counts[stage_title || "Unknown"] += 1
78
+ end
79
+
80
+ puts "Pipeline breakdown:"
81
+ stage_counts.each do |stage, count|
82
+ puts " #{stage}: #{count} deals"
83
+ end
84
+
85
+ # Calculate total pipeline value
86
+ total_value = all_deals.sum do |d|
87
+ d.value.is_a?(Hash) ? (d.value["currency_value"] || 0) : 0
88
+ end
89
+
90
+ puts "\nTotal pipeline value: $#{total_value}"
91
+
92
+ puts "\n=== Working with Deal Relationships ==="
93
+
94
+ # Get associated company (if available)
95
+ if deal.company
96
+ begin
97
+ company = deal.company_record
98
+ puts "Deal is associated with: #{company.name}" if company
99
+ rescue => e
100
+ puts "Could not fetch company: #{e.message}"
101
+ end
102
+ end
103
+
104
+ puts "\n=== Cleanup ==="
105
+
106
+ # Clean up test deals
107
+ [deal, partnership_deal].each do |d|
108
+ d.destroy if d.persisted?
109
+ puts "Deleted deal: #{d.name}"
110
+ end
111
+
112
+ puts "\nDone!"
@@ -223,22 +223,22 @@ class OAuthApp < Sinatra::Base
223
223
 
224
224
  # Test 3: List People Records
225
225
  begin
226
- people = Attio::Record.list(object: "people", limit: 3)
226
+ people = Attio::Person.list(limit: 3)
227
227
  results[:people] = people.count
228
- puts "✓ Record.list (people) successful (#{people.count} records)"
228
+ puts "✓ Person.list successful (#{people.count} records)"
229
229
  rescue => e
230
230
  errors[:people] = e.message
231
- puts "✗ Record.list (people) failed: #{e.message}"
231
+ puts "✗ Person.list failed: #{e.message}"
232
232
  end
233
233
 
234
234
  # Test 4: List Companies Records
235
235
  begin
236
- companies = Attio::Record.list(object: "companies", limit: 3)
236
+ companies = Attio::Company.list(limit: 3)
237
237
  results[:companies] = companies.count
238
- puts "✓ Record.list (companies) successful (#{companies.count} records)"
238
+ puts "✓ Company.list successful (#{companies.count} records)"
239
239
  rescue => e
240
240
  errors[:companies] = e.message
241
- puts "✗ Record.list (companies) failed: #{e.message}"
241
+ puts "✗ Company.list failed: #{e.message}"
242
242
  end
243
243
 
244
244
  # Test 5: List Lists
@@ -399,23 +399,17 @@ class OAuthApp < Sinatra::Base
399
399
  # Test 3: Create a Person
400
400
  person = nil
401
401
  begin
402
- person = Attio::Record.create(
403
- object: "people",
404
- values: {
405
- name: [{
406
- first_name: "OAuth",
407
- last_name: "Test #{Time.now.to_i}",
408
- full_name: "OAuth Test #{Time.now.to_i}"
409
- }],
410
- email_addresses: ["oauth-#{Time.now.to_i}@example.com"]
411
- }
402
+ person = Attio::Person.create(
403
+ first_name: "OAuth",
404
+ last_name: "Test #{Time.now.to_i}",
405
+ email: "oauth-#{Time.now.to_i}@example.com"
412
406
  )
413
407
  created_resources << {type: "person", id: person.id["record_id"]}
414
408
  results[:create_person] = "Created person ID: #{person.id["record_id"][0..10]}..."
415
- puts "✓ Record.create (person) successful"
409
+ puts "✓ Person.create successful"
416
410
  rescue => e
417
411
  errors[:create_person] = e.message
418
- puts "✗ Record.create failed: #{e.message}"
412
+ puts "✗ Person.create failed: #{e.message}"
419
413
  end
420
414
 
421
415
  # Test 4: Create a Note
@@ -425,15 +419,12 @@ class OAuthApp < Sinatra::Base
425
419
  puts " person.id: #{person.id.inspect}"
426
420
  puts " person.id['record_id']: #{person.id["record_id"].inspect}"
427
421
 
428
- note_params = {
422
+ Attio::Note.create(
429
423
  parent_object: "people",
430
424
  parent_record_id: person.id["record_id"],
431
425
  content: "Test note created via OAuth at #{Time.now}",
432
426
  format: "plaintext"
433
- }
434
- puts " note_params: #{note_params.inspect}"
435
-
436
- Attio::Note.create(note_params)
427
+ )
437
428
  results[:create_note] = "Created note on person"
438
429
  puts "✓ Note.create successful"
439
430
  rescue => e
@@ -571,31 +562,23 @@ class OAuthApp < Sinatra::Base
571
562
  # Test 12: Update Operations
572
563
  if person
573
564
  begin
574
- Attio::Record.update(
575
- object: "people",
576
- record_id: person.id["record_id"],
577
- data: {
578
- values: {
579
- name: [{
580
- first_name: "OAuth",
581
- last_name: "Test Updated #{Time.now.to_i}",
582
- full_name: "OAuth Test Updated #{Time.now.to_i}"
583
- }]
584
- }
585
- }
565
+ person.set_name(
566
+ first: "OAuth",
567
+ last: "Test Updated #{Time.now.to_i}"
586
568
  )
569
+ person.save
587
570
  results[:update_record] = "Updated person name"
588
- puts "✓ Record.update successful"
571
+ puts "✓ Person.update successful"
589
572
  rescue => e
590
573
  errors[:update_record] = e.message
591
- puts "✗ Record.update failed: #{e.message}"
574
+ puts "✗ Person.update failed: #{e.message}"
592
575
  end
593
576
  end
594
577
 
595
578
  # Test 13: Error Handling
596
579
  begin
597
580
  # Use a properly formatted UUID that doesn't exist
598
- Attio::Record.retrieve(object: "people", record_id: "00000000-0000-0000-0000-000000000000")
581
+ Attio::Person.retrieve("00000000-0000-0000-0000-000000000000")
599
582
  rescue Attio::NotFoundError => e
600
583
  results[:error_handling] = "404 errors handled correctly"
601
584
  puts "✓ Error handling working correctly"
@@ -620,7 +603,7 @@ class OAuthApp < Sinatra::Base
620
603
  case resource[:type]
621
604
  when "person"
622
605
  # Need to retrieve the record first to call destroy on the instance
623
- record = Attio::Record.retrieve(object: "people", record_id: resource[:id])
606
+ record = Attio::Person.retrieve(resource[:id])
624
607
  record.destroy
625
608
  cleanup_count += 1
626
609
  when "task"
@@ -729,16 +712,10 @@ class OAuthApp < Sinatra::Base
729
712
  begin
730
713
  # Create a test person first
731
714
  timestamp = Time.now.to_i
732
- person = Attio::Record.create(
733
- object: "people",
734
- values: {
735
- name: [{
736
- first_name: "Note",
737
- last_name: "Test#{timestamp}",
738
- full_name: "Note Test#{timestamp}"
739
- }],
740
- email_addresses: ["note-test-#{timestamp}@example.com"]
741
- }
715
+ person = Attio::Person.create(
716
+ first_name: "Note",
717
+ last_name: "Test#{timestamp}",
718
+ email: "note-test-#{timestamp}@example.com"
742
719
  )
743
720
 
744
721
  result_html = "<h1>Note Creation Test</h1>"
@@ -73,10 +73,12 @@ puts <<~NEW
73
73
  puts person.email # => "john@example.com"
74
74
  puts person.phone # => "+12125551234"
75
75
 
76
- # Searching is simpler
76
+ # Searching is simpler with Rails-style find_by
77
77
  people = Attio::Person.search("john")
78
- people = Attio::Person.find_by_email("john@example.com")
79
- people = Attio::Person.find_by_name("John Doe")
78
+ person = Attio::Person.find_by(email: "john@example.com")
79
+ person = Attio::Person.find_by(name: "John Doe")
80
+ # Can combine multiple conditions
81
+ person = Attio::Person.find_by(email: "john@example.com", job_title: "CEO")
80
82
 
81
83
  # Creating a company (no more array wrapping for simple names!)
82
84
  company = Attio::Company.create(
@@ -103,10 +105,11 @@ puts <<~NEW
103
105
  # Find company's team members
104
106
  team = company.team_members
105
107
 
106
- # Find companies by various criteria
107
- company = Attio::Company.find_by_domain("acme.com")
108
- company = Attio::Company.find_by_name("Acme")
109
- large_companies = Attio::Company.find_by_size(100) # 100+ employees
108
+ # Find companies by various criteria using Rails-style find_by
109
+ company = Attio::Company.find_by(domain: "acme.com")
110
+ company = Attio::Company.find_by(name: "Acme")
111
+ # Can combine multiple conditions
112
+ tech_giant = Attio::Company.find_by(domain: "tech.com", employee_count: "1000+")
110
113
  NEW
111
114
 
112
115
  # Working example (if API key is set)
@@ -42,7 +42,9 @@ module Attio
42
42
  validate_object_identifier!(object)
43
43
 
44
44
  # Extract query parameters from opts
45
- query_params = build_query_params(opts)
45
+ # Handle both opts[:params] (from find_by) and direct opts (from other callers)
46
+ params = opts[:params] || opts
47
+ query_params = build_query_params(params)
46
48
 
47
49
  response = execute_request(:POST, "#{resource_path}/#{object}/records/query", query_params, opts)
48
50
 
@@ -80,7 +82,7 @@ module Attio
80
82
 
81
83
  # Ensure object info is included
82
84
  record_data = response["data"] || {}
83
- record_data[:object_api_slug] ||= object if record_data.is_a?(Hash)
85
+ record_data["object_api_slug"] ||= object if record_data.is_a?(Hash)
84
86
 
85
87
  new(record_data, opts)
86
88
  end
@@ -96,7 +98,7 @@ module Attio
96
98
  response = execute_request(:GET, "#{resource_path}/#{object}/records/#{simple_record_id}", {}, opts)
97
99
 
98
100
  record_data = response["data"] || {}
99
- record_data[:object_api_slug] ||= object
101
+ record_data["object_api_slug"] ||= object if record_data.is_a?(Hash)
100
102
 
101
103
  new(record_data, opts)
102
104
  end
@@ -120,7 +122,7 @@ module Attio
120
122
  response = execute_request(:PUT, "#{resource_path}/#{object}/records/#{simple_record_id}", request_params, opts)
121
123
 
122
124
  record_data = response["data"] || {}
123
- record_data[:object_api_slug] ||= object
125
+ record_data["object_api_slug"] ||= object if record_data.is_a?(Hash)
124
126
 
125
127
  new(record_data, opts)
126
128
  end
@@ -188,7 +190,7 @@ module Attio
188
190
 
189
191
  # Attributes that should be sent as simple arrays of strings or simple values
190
192
  SIMPLE_ARRAY_ATTRIBUTES = %w[email_addresses domains].freeze
191
- SIMPLE_VALUE_ATTRIBUTES = %w[description linkedin job_title employee_count].freeze
193
+ SIMPLE_VALUE_ATTRIBUTES = %w[description linkedin job_title employee_count value stage status close_date probability owner].freeze
192
194
  # Attributes that are arrays of objects and should be sent as-is
193
195
  OBJECT_ARRAY_ATTRIBUTES = %w[phone_numbers primary_location company].freeze
194
196
 
@@ -338,9 +340,16 @@ module Attio
338
340
  when Hash
339
341
  if value_data.key?(:value) || value_data.key?("value")
340
342
  value_data[:value] || value_data["value"]
341
- elsif value_data.key?(:target_object) || value_data.key?("target_object")
342
- # Reference value
343
- value_data[:target_object] || value_data["target_object"]
343
+ elsif value_data.key?(:target_object) || value_data.key?("target_object") ||
344
+ value_data.key?(:referenced_actor_type) || value_data.key?("referenced_actor_type")
345
+ # Reference value - return the full reference object
346
+ value_data
347
+ elsif value_data.key?(:currency_value) || value_data.key?("currency_value")
348
+ # Currency value - return the full object to preserve currency info
349
+ value_data
350
+ elsif value_data.key?(:status) || value_data.key?("status")
351
+ # Status value - return the full object to preserve status info
352
+ value_data
344
353
  else
345
354
  value_data
346
355
  end
@@ -141,7 +141,10 @@ module Attio
141
141
  company_id = id.is_a?(Hash) ? id["record_id"] : id
142
142
  Person.list(**opts.merge(params: {
143
143
  filter: {
144
- company: {"$references": company_id}
144
+ company: {
145
+ target_object: "companies",
146
+ target_record_id: company_id
147
+ }
145
148
  }
146
149
  }))
147
150
  end
@@ -173,29 +176,6 @@ module Attio
173
176
  super(values: values, **opts)
174
177
  end
175
178
 
176
- # Find a company by domain
177
- # @param domain [String] Domain to search for
178
- def find_by_domain(domain, **opts)
179
- # Normalize domain
180
- domain = domain.sub(/^https?:\/\//, "")
181
-
182
- list(**opts.merge(
183
- filter: {
184
- domains: {
185
- domain: {
186
- "$eq": domain
187
- }
188
- }
189
- }
190
- )).first
191
- end
192
-
193
- # Find companies by name
194
- # @param name [String] Name to search for
195
- def find_by_name(name, **opts)
196
- results = search(name, **opts)
197
- results.first
198
- end
199
179
 
200
180
  # Find companies by employee count range
201
181
  # @param min [Integer] Minimum employee count
@@ -216,6 +196,28 @@ module Attio
216
196
 
217
197
  list(**opts.merge(params: {filter: filter}))
218
198
  end
199
+
200
+ private
201
+
202
+ # Build filter for domain field
203
+ def filter_by_domain(value)
204
+ # Strip protocol if present
205
+ normalized_domain = value.sub(/^https?:\/\//, "")
206
+ {
207
+ domains: {
208
+ domain: {
209
+ "$eq": normalized_domain
210
+ }
211
+ }
212
+ }
213
+ end
214
+
215
+ # Build filter for name field
216
+ def filter_by_name(value)
217
+ {
218
+ name: {"$contains": value}
219
+ }
220
+ end
219
221
  end
220
222
  end
221
223