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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +164 -0
  4. data/.simplecov +17 -0
  5. data/.yardopts +9 -0
  6. data/CHANGELOG.md +27 -0
  7. data/CONTRIBUTING.md +333 -0
  8. data/INTEGRATION_TEST_STATUS.md +149 -0
  9. data/LICENSE +21 -0
  10. data/README.md +638 -0
  11. data/Rakefile +8 -0
  12. data/attio-ruby.gemspec +61 -0
  13. data/docs/CODECOV_SETUP.md +34 -0
  14. data/examples/basic_usage.rb +149 -0
  15. data/examples/oauth_flow.rb +843 -0
  16. data/examples/oauth_flow_README.md +84 -0
  17. data/examples/typed_records_example.rb +167 -0
  18. data/examples/webhook_server.rb +463 -0
  19. data/lib/attio/api_resource.rb +539 -0
  20. data/lib/attio/builders/name_builder.rb +181 -0
  21. data/lib/attio/client.rb +160 -0
  22. data/lib/attio/errors.rb +126 -0
  23. data/lib/attio/internal/record.rb +359 -0
  24. data/lib/attio/oauth/client.rb +219 -0
  25. data/lib/attio/oauth/scope_validator.rb +162 -0
  26. data/lib/attio/oauth/token.rb +158 -0
  27. data/lib/attio/resources/attribute.rb +332 -0
  28. data/lib/attio/resources/comment.rb +114 -0
  29. data/lib/attio/resources/company.rb +224 -0
  30. data/lib/attio/resources/entry.rb +208 -0
  31. data/lib/attio/resources/list.rb +196 -0
  32. data/lib/attio/resources/meta.rb +113 -0
  33. data/lib/attio/resources/note.rb +213 -0
  34. data/lib/attio/resources/object.rb +66 -0
  35. data/lib/attio/resources/person.rb +294 -0
  36. data/lib/attio/resources/task.rb +147 -0
  37. data/lib/attio/resources/thread.rb +99 -0
  38. data/lib/attio/resources/typed_record.rb +98 -0
  39. data/lib/attio/resources/webhook.rb +224 -0
  40. data/lib/attio/resources/workspace_member.rb +136 -0
  41. data/lib/attio/util/configuration.rb +166 -0
  42. data/lib/attio/util/id_extractor.rb +115 -0
  43. data/lib/attio/util/webhook_signature.rb +175 -0
  44. data/lib/attio/version.rb +6 -0
  45. data/lib/attio/webhook/event.rb +114 -0
  46. data/lib/attio/webhook/signature_verifier.rb +73 -0
  47. data/lib/attio.rb +123 -0
  48. metadata +402 -0
data/README.md ADDED
@@ -0,0 +1,638 @@
1
+ # Attio Ruby SDK
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/attio-ruby.svg)](https://badge.fury.io/rb/attio-ruby)
4
+ [![Build Status](https://github.com/rbeene/attio_ruby/workflows/CI/badge.svg)](https://github.com/rbeene/attio_ruby/actions)
5
+ [![Documentation](https://img.shields.io/badge/docs-YARD-blue.svg)](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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -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