attio-ruby 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +164 -0
- data/.simplecov +17 -0
- data/.yardopts +9 -0
- data/CHANGELOG.md +27 -0
- data/CONTRIBUTING.md +333 -0
- data/INTEGRATION_TEST_STATUS.md +149 -0
- data/LICENSE +21 -0
- data/README.md +638 -0
- data/Rakefile +8 -0
- data/attio-ruby.gemspec +61 -0
- data/docs/CODECOV_SETUP.md +34 -0
- data/examples/basic_usage.rb +149 -0
- data/examples/oauth_flow.rb +843 -0
- data/examples/oauth_flow_README.md +84 -0
- data/examples/typed_records_example.rb +167 -0
- data/examples/webhook_server.rb +463 -0
- data/lib/attio/api_resource.rb +539 -0
- data/lib/attio/builders/name_builder.rb +181 -0
- data/lib/attio/client.rb +160 -0
- data/lib/attio/errors.rb +126 -0
- data/lib/attio/internal/record.rb +359 -0
- data/lib/attio/oauth/client.rb +219 -0
- data/lib/attio/oauth/scope_validator.rb +162 -0
- data/lib/attio/oauth/token.rb +158 -0
- data/lib/attio/resources/attribute.rb +332 -0
- data/lib/attio/resources/comment.rb +114 -0
- data/lib/attio/resources/company.rb +224 -0
- data/lib/attio/resources/entry.rb +208 -0
- data/lib/attio/resources/list.rb +196 -0
- data/lib/attio/resources/meta.rb +113 -0
- data/lib/attio/resources/note.rb +213 -0
- data/lib/attio/resources/object.rb +66 -0
- data/lib/attio/resources/person.rb +294 -0
- data/lib/attio/resources/task.rb +147 -0
- data/lib/attio/resources/thread.rb +99 -0
- data/lib/attio/resources/typed_record.rb +98 -0
- data/lib/attio/resources/webhook.rb +224 -0
- data/lib/attio/resources/workspace_member.rb +136 -0
- data/lib/attio/util/configuration.rb +166 -0
- data/lib/attio/util/id_extractor.rb +115 -0
- data/lib/attio/util/webhook_signature.rb +175 -0
- data/lib/attio/version.rb +6 -0
- data/lib/attio/webhook/event.rb +114 -0
- data/lib/attio/webhook/signature_verifier.rb +73 -0
- data/lib/attio.rb +123 -0
- metadata +402 -0
data/README.md
ADDED
@@ -0,0 +1,638 @@
|
|
1
|
+
# Attio Ruby SDK
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/attio-ruby)
|
4
|
+
[](https://github.com/rbeene/attio_ruby/actions)
|
5
|
+
[](https://rubydoc.info/gems/attio-ruby)
|
6
|
+
|
7
|
+
A Ruby SDK for the [Attio API](https://attio.com/docs). This gem provides a simple and intuitive interface for interacting with Attio's CRM platform.
|
8
|
+
|
9
|
+
## Table of Contents
|
10
|
+
|
11
|
+
- [Installation](#installation)
|
12
|
+
- [Quick Start](#quick-start)
|
13
|
+
- [Configuration](#configuration)
|
14
|
+
- [Authentication](#authentication)
|
15
|
+
- [Basic Usage](#basic-usage)
|
16
|
+
- [Working with Objects](#working-with-objects)
|
17
|
+
- [Managing Records](#managing-records)
|
18
|
+
- [Lists and List Entries](#lists-and-list-entries)
|
19
|
+
- [Notes](#notes)
|
20
|
+
- [Webhooks](#webhooks)
|
21
|
+
- [Advanced Features](#advanced-features)
|
22
|
+
- [OAuth 2.0](#oauth-20)
|
23
|
+
- [Error Handling](#error-handling)
|
24
|
+
- [Examples](#examples)
|
25
|
+
- [Testing](#testing)
|
26
|
+
- [Performance](#performance)
|
27
|
+
- [Contributing](#contributing)
|
28
|
+
- [License](#license)
|
29
|
+
|
30
|
+
## Installation
|
31
|
+
|
32
|
+
Add this line to your application's Gemfile:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
gem 'attio-ruby'
|
36
|
+
```
|
37
|
+
|
38
|
+
And then execute:
|
39
|
+
|
40
|
+
```bash
|
41
|
+
$ bundle install
|
42
|
+
```
|
43
|
+
|
44
|
+
Or install it yourself as:
|
45
|
+
|
46
|
+
```bash
|
47
|
+
$ gem install attio-ruby
|
48
|
+
```
|
49
|
+
|
50
|
+
## Quick Start
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require 'attio'
|
54
|
+
|
55
|
+
# Configure the client
|
56
|
+
Attio.configure do |config|
|
57
|
+
config.api_key = ENV['ATTIO_API_KEY']
|
58
|
+
end
|
59
|
+
|
60
|
+
# Create a person
|
61
|
+
person = Attio::Person.create(
|
62
|
+
first_name: "John",
|
63
|
+
last_name: "Doe",
|
64
|
+
email: "john@example.com"
|
65
|
+
)
|
66
|
+
|
67
|
+
# Search for companies
|
68
|
+
companies = Attio::Company.list(
|
69
|
+
params: { q: "tech", limit: 10 }
|
70
|
+
)
|
71
|
+
```
|
72
|
+
|
73
|
+
## Configuration
|
74
|
+
|
75
|
+
The gem can be configured globally or on a per-request basis:
|
76
|
+
|
77
|
+
### Global Configuration
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
Attio.configure do |config|
|
81
|
+
# Required
|
82
|
+
config.api_key = "your_api_key"
|
83
|
+
|
84
|
+
# Optional
|
85
|
+
config.api_base = "https://api.attio.com" # Default
|
86
|
+
config.api_version = "v2" # Default
|
87
|
+
config.timeout = 30 # Request timeout in seconds
|
88
|
+
config.max_retries = 3 # Number of retries for failed requests
|
89
|
+
config.debug = false # Enable debug logging
|
90
|
+
config.logger = Logger.new(STDOUT) # Custom logger
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
### Environment Variables
|
95
|
+
|
96
|
+
The gem automatically reads configuration from environment variables:
|
97
|
+
|
98
|
+
- `ATTIO_API_KEY` - Your API key
|
99
|
+
- `ATTIO_API_BASE` - API base URL (optional)
|
100
|
+
- `ATTIO_DEBUG` - Enable debug mode (optional)
|
101
|
+
|
102
|
+
### Per-Request Configuration
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
# Override configuration for a single request
|
106
|
+
person = Attio::Person.create(
|
107
|
+
first_name: "Jane",
|
108
|
+
last_name: "Doe",
|
109
|
+
api_key: "different_api_key"
|
110
|
+
)
|
111
|
+
```
|
112
|
+
|
113
|
+
## Authentication
|
114
|
+
|
115
|
+
### API Key Authentication
|
116
|
+
|
117
|
+
The simplest way to authenticate is using an API key:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
Attio.configure do |config|
|
121
|
+
config.api_key = "your_api_key"
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
### OAuth 2.0 Authentication
|
126
|
+
|
127
|
+
For user-facing applications, use OAuth 2.0. The gem includes OAuth support, but for a complete OAuth integration example, see our companion Rails application (coming soon).
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
# Initialize OAuth client
|
131
|
+
oauth_client = Attio::OAuth::Client.new(
|
132
|
+
client_id: ENV['ATTIO_CLIENT_ID'],
|
133
|
+
client_secret: ENV['ATTIO_CLIENT_SECRET'],
|
134
|
+
redirect_uri: "https://yourapp.com/callback"
|
135
|
+
)
|
136
|
+
|
137
|
+
# Generate authorization URL
|
138
|
+
auth_data = oauth_client.authorization_url(
|
139
|
+
scopes: %w[record:read record:write],
|
140
|
+
state: "random_state"
|
141
|
+
)
|
142
|
+
redirect_to auth_data[:url]
|
143
|
+
|
144
|
+
# Exchange code for token
|
145
|
+
token = oauth_client.exchange_code_for_token(code: params[:code])
|
146
|
+
|
147
|
+
# Use the token
|
148
|
+
Attio.configure do |config|
|
149
|
+
config.api_key = token.access_token
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
## Basic Usage
|
154
|
+
|
155
|
+
### Working with Objects
|
156
|
+
|
157
|
+
Objects represent the different types of records in your workspace (e.g., People, Companies).
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
# List all objects
|
161
|
+
objects = Attio::Object.list
|
162
|
+
objects.each do |object|
|
163
|
+
puts "#{object.plural_noun} (#{object.api_slug})"
|
164
|
+
end
|
165
|
+
|
166
|
+
# Get a specific object
|
167
|
+
people_object = Attio::Object.retrieve("people")
|
168
|
+
puts people_object.name # => "People"
|
169
|
+
```
|
170
|
+
|
171
|
+
### Managing Records
|
172
|
+
|
173
|
+
Records are instances of objects (e.g., individual people or companies). The gem provides typed classes (`Attio::Person`, `Attio::Company`) that inherit from `TypedRecord`, offering a cleaner interface than the generic `Attio::Record` class.
|
174
|
+
|
175
|
+
#### Complex Attributes
|
176
|
+
|
177
|
+
The gem provides convenient methods for working with complex attributes. You can use the simplified interface or the raw API format:
|
178
|
+
|
179
|
+
**Simple Interface (Recommended):**
|
180
|
+
```ruby
|
181
|
+
# The gem handles the complex structure for you
|
182
|
+
person = Attio::Person.create(
|
183
|
+
first_name: "John",
|
184
|
+
last_name: "Smith",
|
185
|
+
email: "john@example.com",
|
186
|
+
phone: "+15558675309",
|
187
|
+
job_title: "Developer"
|
188
|
+
)
|
189
|
+
|
190
|
+
company = Attio::Company.create(
|
191
|
+
name: "Acme Corp",
|
192
|
+
domain: "acme.com",
|
193
|
+
employee_count: "50-100"
|
194
|
+
)
|
195
|
+
```
|
196
|
+
|
197
|
+
**Raw API Format (Advanced):**
|
198
|
+
If you need full control, you can use the raw API structures:
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
# Names
|
202
|
+
values: {
|
203
|
+
name: [{
|
204
|
+
first_name: "John",
|
205
|
+
last_name: "Smith",
|
206
|
+
full_name: "John Smith"
|
207
|
+
}]
|
208
|
+
}
|
209
|
+
|
210
|
+
# Phone Numbers
|
211
|
+
values: {
|
212
|
+
phone_numbers: [{
|
213
|
+
original_phone_number: "+15558675309",
|
214
|
+
country_code: "US"
|
215
|
+
}]
|
216
|
+
}
|
217
|
+
|
218
|
+
# Addresses
|
219
|
+
values: {
|
220
|
+
primary_location: [{
|
221
|
+
line_1: "1 Infinite Loop",
|
222
|
+
locality: "Cupertino",
|
223
|
+
region: "CA",
|
224
|
+
postcode: "95014",
|
225
|
+
country_code: "US"
|
226
|
+
}]
|
227
|
+
}
|
228
|
+
|
229
|
+
# Email addresses and domains (simple arrays)
|
230
|
+
values: {
|
231
|
+
email_addresses: ["john@example.com", "john.smith@company.com"],
|
232
|
+
domains: ["example.com", "example.org"]
|
233
|
+
}
|
234
|
+
```
|
235
|
+
|
236
|
+
#### Creating Records
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
# Create a person
|
240
|
+
person = Attio::Person.create(
|
241
|
+
first_name: "Jane",
|
242
|
+
last_name: "Smith",
|
243
|
+
email: "jane@example.com",
|
244
|
+
phone: "+1-555-0123",
|
245
|
+
job_title: "CEO"
|
246
|
+
)
|
247
|
+
|
248
|
+
# Create a company
|
249
|
+
company = Attio::Company.create(
|
250
|
+
name: "Acme Corp",
|
251
|
+
domain: "acme.com",
|
252
|
+
values: {
|
253
|
+
industry: "Technology"
|
254
|
+
}
|
255
|
+
)
|
256
|
+
```
|
257
|
+
|
258
|
+
#### Retrieving Records
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
# Get a specific person
|
262
|
+
person = Attio::Person.retrieve("rec_456def789")
|
263
|
+
|
264
|
+
# Access attributes using bracket notation
|
265
|
+
puts person[:name]
|
266
|
+
puts person[:email_addresses]
|
267
|
+
puts person[:job_title]
|
268
|
+
|
269
|
+
# Note: Attributes can be accessed with bracket notation and symbols
|
270
|
+
```
|
271
|
+
|
272
|
+
#### Updating Records
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
# Update a record using attribute setters
|
276
|
+
person[:job_title] = "CTO"
|
277
|
+
person[:tags] = ["vip", "customer"]
|
278
|
+
person.save
|
279
|
+
|
280
|
+
# Or update directly
|
281
|
+
Attio::Person.update(
|
282
|
+
"rec_456def789",
|
283
|
+
values: { job_title: "CTO" }
|
284
|
+
)
|
285
|
+
```
|
286
|
+
|
287
|
+
#### Searching and Filtering
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
# Simple search
|
291
|
+
people = Attio::Person.search("john")
|
292
|
+
|
293
|
+
# Advanced filtering
|
294
|
+
executives = Attio::Person.list(
|
295
|
+
params: {
|
296
|
+
filter: {
|
297
|
+
job_title: { "$contains": "CEO" }
|
298
|
+
},
|
299
|
+
sort: [{ attribute: "name", direction: "asc" }],
|
300
|
+
limit: 20
|
301
|
+
}
|
302
|
+
)
|
303
|
+
|
304
|
+
# Pagination
|
305
|
+
page = people
|
306
|
+
while page.has_more?
|
307
|
+
page.each do |person|
|
308
|
+
puts person[:name]
|
309
|
+
end
|
310
|
+
page = page.next_page
|
311
|
+
end
|
312
|
+
|
313
|
+
# Auto-pagination
|
314
|
+
people.auto_paging_each do |person|
|
315
|
+
puts person[:name]
|
316
|
+
end
|
317
|
+
```
|
318
|
+
|
319
|
+
#### Deleting Records
|
320
|
+
|
321
|
+
```ruby
|
322
|
+
# Delete a record
|
323
|
+
person.destroy
|
324
|
+
|
325
|
+
# Or delete by ID
|
326
|
+
Attio::Person.delete("rec_123abc456") # Replace with actual record ID
|
327
|
+
```
|
328
|
+
|
329
|
+
#### Note on Batch Operations
|
330
|
+
|
331
|
+
The Attio API does not currently support batch operations for creating, updating, or deleting multiple records in a single request. Each record must be processed individually. If you need to process many records, consider implementing rate limiting and error handling in your application.
|
332
|
+
|
333
|
+
### Convenience Methods
|
334
|
+
|
335
|
+
The gem provides many convenience methods to make working with records easier:
|
336
|
+
|
337
|
+
#### Person Methods
|
338
|
+
|
339
|
+
```ruby
|
340
|
+
person = Attio::Person.retrieve("rec_123")
|
341
|
+
|
342
|
+
# Access methods
|
343
|
+
person.email # Returns primary email address
|
344
|
+
person.phone # Returns primary phone number
|
345
|
+
person.first_name # Returns first name
|
346
|
+
person.last_name # Returns last name
|
347
|
+
person.full_name # Returns full name
|
348
|
+
|
349
|
+
# Modification methods
|
350
|
+
person.set_name(first: "Jane", last: "Doe")
|
351
|
+
person.add_email("jane.doe@example.com")
|
352
|
+
person.add_phone("+14155551234", country_code: "US")
|
353
|
+
|
354
|
+
# Search methods
|
355
|
+
jane = Attio::Person.find_by_email("jane@example.com")
|
356
|
+
john = Attio::Person.find_by_name("John Smith")
|
357
|
+
```
|
358
|
+
|
359
|
+
#### Company Methods
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
company = Attio::Company.retrieve("rec_456")
|
363
|
+
|
364
|
+
# Access methods
|
365
|
+
company.name # Returns company name
|
366
|
+
company.domain # Returns primary domain
|
367
|
+
company.domains_list # Returns all domains
|
368
|
+
|
369
|
+
# Modification methods
|
370
|
+
company.name = "New Company Name"
|
371
|
+
company.add_domain("newdomain.com")
|
372
|
+
company.add_team_member(person) # Associate a person with the company
|
373
|
+
|
374
|
+
# Search methods
|
375
|
+
acme = Attio::Company.find_by_name("Acme Corp")
|
376
|
+
tech_co = Attio::Company.find_by_domain("techcompany.com")
|
377
|
+
large_cos = Attio::Company.find_by_size(min: 100)
|
378
|
+
```
|
379
|
+
|
380
|
+
#### TypedRecord Methods
|
381
|
+
|
382
|
+
All typed records (Person, Company, and custom objects) support:
|
383
|
+
|
384
|
+
```ruby
|
385
|
+
# Search with query string
|
386
|
+
results = Attio::Person.search("john")
|
387
|
+
|
388
|
+
# Find by any attribute
|
389
|
+
person = Attio::Person.find_by(:job_title, "CEO")
|
390
|
+
|
391
|
+
# Aliases for common methods
|
392
|
+
Attio::Person.all == Attio::Person.list
|
393
|
+
Attio::Person.find("rec_123") == Attio::Person.retrieve("rec_123")
|
394
|
+
```
|
395
|
+
|
396
|
+
### Lists and List Entries
|
397
|
+
|
398
|
+
Lists allow you to organize records into groups.
|
399
|
+
|
400
|
+
```ruby
|
401
|
+
# Create a list
|
402
|
+
list = Attio::List.create(
|
403
|
+
name: "VIP Customers",
|
404
|
+
object: "people"
|
405
|
+
)
|
406
|
+
|
407
|
+
# Add records to a list
|
408
|
+
entry = list.add_record("rec_789def012") # Replace with actual record ID
|
409
|
+
|
410
|
+
# List entries
|
411
|
+
entries = list.entries
|
412
|
+
entries.each do |entry|
|
413
|
+
puts entry.record_id
|
414
|
+
end
|
415
|
+
|
416
|
+
# Remove from list (requires entry_id, not record_id)
|
417
|
+
list.remove_record("ent_456ghi789") # Replace with actual list entry ID
|
418
|
+
|
419
|
+
# Delete list
|
420
|
+
list.destroy
|
421
|
+
```
|
422
|
+
|
423
|
+
### Notes
|
424
|
+
|
425
|
+
Add notes to records to track interactions and important information.
|
426
|
+
|
427
|
+
```ruby
|
428
|
+
# Create a note
|
429
|
+
note = Attio::Note.create(
|
430
|
+
parent_object: "people",
|
431
|
+
parent_record_id: "rec_123abc456", # Replace with actual record ID
|
432
|
+
content: "Had a great meeting about the new project.",
|
433
|
+
format: "plaintext" # or "markdown"
|
434
|
+
)
|
435
|
+
|
436
|
+
# List notes for a record
|
437
|
+
notes = Attio::Note.list(
|
438
|
+
parent_object: "people",
|
439
|
+
parent_record_id: "rec_123abc456" # Replace with actual record ID
|
440
|
+
)
|
441
|
+
|
442
|
+
# Notes are immutable - create a new note instead of updating
|
443
|
+
# To "update" a note, you would delete the old one and create a new one
|
444
|
+
|
445
|
+
# Delete a note
|
446
|
+
note.destroy
|
447
|
+
```
|
448
|
+
|
449
|
+
### Webhooks
|
450
|
+
|
451
|
+
Set up webhooks to receive real-time updates about changes in your workspace.
|
452
|
+
|
453
|
+
```ruby
|
454
|
+
# Create a webhook
|
455
|
+
webhook = Attio::Webhook.create(
|
456
|
+
name: "Customer Updates",
|
457
|
+
url: "https://yourapp.com/webhooks/attio",
|
458
|
+
subscriptions: %w[record.created record.updated]
|
459
|
+
)
|
460
|
+
|
461
|
+
# List webhooks
|
462
|
+
webhooks = Attio::Webhook.list
|
463
|
+
|
464
|
+
# Update webhook
|
465
|
+
webhook[:active] = false
|
466
|
+
webhook.save
|
467
|
+
|
468
|
+
# Delete webhook
|
469
|
+
webhook.destroy
|
470
|
+
|
471
|
+
# Verify webhook signatures
|
472
|
+
Attio::Util::WebhookSignature.verify!(
|
473
|
+
payload: request.body.read,
|
474
|
+
signature: request.headers['Attio-Signature'],
|
475
|
+
secret: ENV['WEBHOOK_SECRET']
|
476
|
+
)
|
477
|
+
```
|
478
|
+
|
479
|
+
## Advanced Features
|
480
|
+
|
481
|
+
### OAuth 2.0
|
482
|
+
|
483
|
+
Complete OAuth 2.0 flow implementation:
|
484
|
+
|
485
|
+
```ruby
|
486
|
+
# Initialize client
|
487
|
+
oauth = Attio::OAuth::Client.new(
|
488
|
+
client_id: ENV['CLIENT_ID'],
|
489
|
+
client_secret: ENV['CLIENT_SECRET'],
|
490
|
+
redirect_uri: "https://yourapp.com/callback"
|
491
|
+
)
|
492
|
+
|
493
|
+
# Authorization
|
494
|
+
auth_data = oauth.authorization_url(
|
495
|
+
scopes: %w[record:read record:write user:read],
|
496
|
+
state: SecureRandom.hex(16)
|
497
|
+
)
|
498
|
+
|
499
|
+
# Token exchange
|
500
|
+
token = oauth.exchange_code_for_token(
|
501
|
+
code: params[:code],
|
502
|
+
state: params[:state]
|
503
|
+
)
|
504
|
+
|
505
|
+
# Token refresh
|
506
|
+
new_token = oauth.refresh_token("rtok_xyz789ghi012") # Replace with actual refresh token
|
507
|
+
|
508
|
+
# Token introspection
|
509
|
+
info = oauth.introspect_token("tok_abc123def456") # Replace with actual access token
|
510
|
+
puts info[:active] # => true
|
511
|
+
|
512
|
+
# Token revocation
|
513
|
+
oauth.revoke_token("tok_abc123def456") # Replace with actual access token
|
514
|
+
```
|
515
|
+
|
516
|
+
|
517
|
+
### Error Handling
|
518
|
+
|
519
|
+
The gem provides comprehensive error handling:
|
520
|
+
|
521
|
+
```ruby
|
522
|
+
begin
|
523
|
+
person = Attio::Person.create(
|
524
|
+
email: "invalid-email"
|
525
|
+
)
|
526
|
+
rescue Attio::InvalidRequestError => e
|
527
|
+
puts "Validation error: #{e.message}"
|
528
|
+
puts "HTTP status: #{e.code}"
|
529
|
+
puts "Request ID: #{e.request_id}"
|
530
|
+
rescue Attio::AuthenticationError => e
|
531
|
+
puts "Auth failed: #{e.message}"
|
532
|
+
puts "Request ID: #{e.request_id}"
|
533
|
+
rescue Attio::RateLimitError => e
|
534
|
+
puts "Rate limited: #{e.message}"
|
535
|
+
rescue Attio::ConnectionError => e
|
536
|
+
puts "Network error: #{e.message}"
|
537
|
+
rescue Attio::Error => e
|
538
|
+
puts "API error: #{e.message}"
|
539
|
+
puts "HTTP status: #{e.code}"
|
540
|
+
puts "Request ID: #{e.request_id}"
|
541
|
+
end
|
542
|
+
```
|
543
|
+
|
544
|
+
## Examples
|
545
|
+
|
546
|
+
Complete example applications are available in the `examples/` directory:
|
547
|
+
|
548
|
+
- `basic_usage.rb` - Demonstrates core functionality
|
549
|
+
- `oauth_flow.rb` - Complete OAuth 2.0 implementation with Sinatra
|
550
|
+
- `webhook_server.rb` - Webhook handling with signature verification
|
551
|
+
|
552
|
+
Run an example:
|
553
|
+
|
554
|
+
```bash
|
555
|
+
$ ruby examples/basic_usage.rb
|
556
|
+
```
|
557
|
+
|
558
|
+
## Testing
|
559
|
+
|
560
|
+
The gem includes comprehensive test coverage:
|
561
|
+
|
562
|
+
```bash
|
563
|
+
# Run all tests (unit tests only by default)
|
564
|
+
$ bundle exec rspec
|
565
|
+
|
566
|
+
# Run unit tests only
|
567
|
+
$ bundle exec rspec spec/unit
|
568
|
+
|
569
|
+
# Run integration tests (requires API key)
|
570
|
+
$ RUN_INTEGRATION_TESTS=true bundle exec rspec spec/integration
|
571
|
+
```
|
572
|
+
|
573
|
+
### Integration Tests
|
574
|
+
|
575
|
+
**Note**: This gem is under active development. To ensure our implementation matches the Attio API, we leverage live integration tests against a sandbox environment. This strategy will be removed once we hit a stable 1.0 release.
|
576
|
+
|
577
|
+
Integration tests make real API calls to Attio and are disabled by default. They serve to:
|
578
|
+
|
579
|
+
- Validate that our WebMock stubs match actual API behavior
|
580
|
+
- Test OAuth flows and complex scenarios
|
581
|
+
- Ensure the gem works correctly with the latest Attio API
|
582
|
+
|
583
|
+
To run integration tests:
|
584
|
+
|
585
|
+
1. Set up your environment variables:
|
586
|
+
```bash
|
587
|
+
export ATTIO_API_KEY="your_api_key"
|
588
|
+
export RUN_INTEGRATION_TESTS=true
|
589
|
+
```
|
590
|
+
|
591
|
+
2. Run the tests:
|
592
|
+
```bash
|
593
|
+
bundle exec rspec spec/integration
|
594
|
+
```
|
595
|
+
|
596
|
+
**Warning**: Integration tests will create and delete real data in your Attio workspace. They include automatic cleanup, but use a test workspace if possible.
|
597
|
+
|
598
|
+
### Unit Tests
|
599
|
+
|
600
|
+
Unit tests use WebMock to stub all HTTP requests and do not require an API key. They run by default and ensure the gem's internal logic works correctly.
|
601
|
+
|
602
|
+
```bash
|
603
|
+
# Run only unit tests
|
604
|
+
bundle exec rspec spec/unit
|
605
|
+
|
606
|
+
# Run with coverage
|
607
|
+
$ COVERAGE=true bundle exec rspec
|
608
|
+
```
|
609
|
+
|
610
|
+
## Performance
|
611
|
+
|
612
|
+
The gem is optimized for performance:
|
613
|
+
|
614
|
+
- Connection pooling for HTTP keep-alive
|
615
|
+
- Automatic retry with exponential backoff
|
616
|
+
- Efficient pagination with auto-paging
|
617
|
+
- Thread-safe operations
|
618
|
+
|
619
|
+
Run benchmarks:
|
620
|
+
|
621
|
+
```bash
|
622
|
+
$ ruby benchmarks/api_performance.rb
|
623
|
+
$ ruby benchmarks/memory_profile.rb
|
624
|
+
```
|
625
|
+
|
626
|
+
## Contributing
|
627
|
+
|
628
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
629
|
+
|
630
|
+
1. Fork the repository
|
631
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
632
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
633
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
634
|
+
5. Open a Pull Request
|
635
|
+
|
636
|
+
## License
|
637
|
+
|
638
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE).
|
data/Rakefile
ADDED
data/attio-ruby.gemspec
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/attio/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "attio-ruby"
|
7
|
+
spec.version = Attio::VERSION
|
8
|
+
spec.authors = ["Robert Beene"]
|
9
|
+
spec.email = ["robert@ismly.com"]
|
10
|
+
|
11
|
+
spec.summary = "Ruby client library for the Attio API"
|
12
|
+
spec.description = "A comprehensive Ruby client library for the Attio CRM API with OAuth support, type safety, and extensive test coverage"
|
13
|
+
spec.homepage = "https://github.com/rbeene/attio_ruby"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.4.0"
|
16
|
+
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/rbeene/attio_ruby"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/rbeene/attio_ruby/blob/main/CHANGELOG.md"
|
21
|
+
spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/attio-ruby"
|
22
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/rbeene/attio_ruby/issues"
|
23
|
+
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(File.expand_path(f) == __FILE__) ||
|
28
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
# Runtime dependencies
|
36
|
+
spec.add_dependency "faraday", "~> 2.0"
|
37
|
+
spec.add_dependency "faraday-retry", "~> 2.0"
|
38
|
+
spec.add_dependency "ostruct", "~> 0.6"
|
39
|
+
|
40
|
+
# Development dependencies
|
41
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
42
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
43
|
+
spec.add_development_dependency "rspec", "~> 3.12"
|
44
|
+
spec.add_development_dependency "webmock", "~> 3.18"
|
45
|
+
spec.add_development_dependency "simplecov", "~> 0.22"
|
46
|
+
spec.add_development_dependency "simplecov-cobertura", "~> 2.1"
|
47
|
+
spec.add_development_dependency "yard", "~> 0.9"
|
48
|
+
spec.add_development_dependency "redcarpet", "~> 3.6"
|
49
|
+
spec.add_development_dependency "rubocop", "~> 1.50"
|
50
|
+
spec.add_development_dependency "rubocop-rspec", "~> 2.20"
|
51
|
+
spec.add_development_dependency "rubocop-performance", "~> 1.17"
|
52
|
+
spec.add_development_dependency "standard", "~> 1.28"
|
53
|
+
spec.add_development_dependency "benchmark-ips", "~> 2.12"
|
54
|
+
spec.add_development_dependency "pry", "~> 0.14"
|
55
|
+
spec.add_development_dependency "pry-byebug", "~> 3.10"
|
56
|
+
spec.add_development_dependency "dotenv", "~> 2.8"
|
57
|
+
spec.add_development_dependency "timecop", "~> 0.9"
|
58
|
+
spec.add_development_dependency "bundle-audit", "~> 0.1"
|
59
|
+
spec.add_development_dependency "brakeman", "~> 6.0"
|
60
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
61
|
+
end
|